GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • 卡码笔记-最强八股文
  • 计算机基础
  • Python
  • C++
  • Java
  • Go
  • ChatGPT充值
  • Claude充值
  • 国内使用Claude
  • 中转站推荐
  • 🔥大模型🔥
  • 大模型面经
  • Java面经
  • C++面经

This view

  • Original page
  • New address

GeezerVison is showing notes.kamacoder.com — Python模块和包是什么?import是怎么工作的?模块导入机制与sys.path

# Python模块和包是什么?import是怎么工作的?

面试官问:"Python的模块和包有什么区别?import是如何查找和加载模块的?"

很多人只知道"模块是.py文件,包是包含__init__.py的目录",但说不清楚import的查找顺序、模块缓存、循环导入的原因,以及from import和import的区别。

# 简要回答

模块是一个.py文件,包含Python定义和语句。模块可以定义函数、类和变量,也可以包含可执行代码。

包是包含__init__.py文件的目录,用于组织多个模块。包可以嵌套,形成层级结构。

import执行时,Python按sys.path顺序查找模块,找到后编译并执行模块代码,将模块对象缓存到sys.modules。后续import直接从缓存返回,不会重复执行。

import module导入模块对象,from module import name导入模块中的名字。前者访问时需要module.name,后者直接用name。

# 详细回答

# 模块

模块是一个.py文件,文件名去掉.py就是模块名:

# math_utils.py
def add(a, b):
    return a + b

PI = 3.14159
1
2
3
4
5

导入模块:

import math_utils

print(math_utils.add(2, 3))  # 5
print(math_utils.PI)         # 3.14159
1
2
3
4

# 包

包是包含__init__.py的目录,用于组织多个模块:

my_package/
    __init__.py
    module_a.py
    module_b.py
1
2
3
4

导入包中的模块:

import my_package.module_a
from my_package import module_b

my_package.module_a.func()
module_b.func()
1
2
3
4
5

__init__.py可以是空文件,也可以包含包的初始化代码。Python 3.3+支持隐式命名空间包(没有__init__.py),但显式包仍然是推荐做法。

# import的查找顺序

import module_name时,Python按以下顺序查找:

  1. sys.modules缓存:已导入的模块直接返回。
  2. 内置模块:如sys、os、math。
  3. sys.path列表中的路径,依次查找:
    • 当前脚本所在目录(或交互式环境的当前目录)。
    • PYTHONPATH环境变量中的目录。
    • 标准库目录。
    • 第三方库目录(site-packages)。

找到模块后,Python编译为字节码(如果需要),执行模块代码,创建模块对象并缓存到sys.modules。

# 查看sys.path

import sys
print(sys.path)
# ['当前目录', '/usr/lib/python3.x', '/usr/lib/python3.x/site-packages', ...]
1
2
3

可以动态修改sys.path,但不推荐:

sys.path.append('/custom/path')
import custom_module
1
2

# import与from import

# import导入模块对象
import math
print(math.sqrt(16))  # 4.0

# from import导入模块中的名字
from math import sqrt
print(sqrt(16))  # 4.0

# 导入多个名字
from math import sqrt, pi

# 导入所有名字(不推荐)
from math import *
1
2
3
4
5
6
7
8
9
10
11
12
13

from math import *会污染命名空间,隐藏了名字来源,降低可读性。

import查找与加载流程

# 模块缓存sys.modules

模块只会被导入一次,后续import直接从缓存返回:

# first.py
print("first.py is being imported")

# main.py
import first  # first.py is being imported
import first  # 不会再次打印,直接从缓存返回
1
2
3
4
5
6

可以查看缓存:

import sys
import math

print('math' in sys.modules)  # True
print(sys.modules['math'])    # <module 'math' from '...'>
1
2
3
4
5

强制重新加载模块(不推荐):

import importlib
importlib.reload(math)
1
2

# 相对导入与绝对导入

绝对导入:从sys.path根开始查找:

from my_package.module_a import func
1

相对导入:从当前包的位置开始查找:

# my_package/module_b.py
from . import module_a         # 导入同级模块
from .module_a import func     # 导入同级模块中的名字
from .. import parent_module   # 导入上级包中的模块
1
2
3
4

相对导入只能在包内使用,不能在顶层脚本中使用。

# 循环导入问题

两个模块互相导入会导致循环导入:

# a.py
import b
def func_a():
    b.func_b()

# b.py
import a
def func_b():
    a.func_a()
1
2
3
4
5
6
7
8
9

Python允许循环导入,但如果导入时立即访问对方模块的名字,会出错:

# a.py
import b
print(b.var)  # AttributeError: module 'b' has no attribute 'var'

# b.py
import a
var = 10
1
2
3
4
5
6
7

导入a时,a导入b,b导入a(此时a还没执行完),b执行完后返回a,a继续执行print(b.var)。此时b已经完成,b.var存在,不会出错。但如果a在导入时就访问b的名字,而b还没定义,就会出错。

解决方法:

  1. 延迟导入:在函数内导入。
  2. 重构代码:把共同依赖提取到第三个模块。
  3. 导入模块而不是名字:import b而不是from b import func。

# __name__变量

每个模块有__name__变量,表示模块名:

# module.py
print(__name__)  # 'module'

# main.py
import module  # 输出 'module'
1
2
3
4
5

当模块作为脚本直接运行时,__name__是'__main__':

# script.py
if __name__ == '__main__':
    print("This is the main script")
1
2
3

这是Python的常见模式:模块既可以被导入,也可以作为脚本运行。

# 知识拓展

Q1:__init__.py里应该写什么?

可以写包的初始化代码、导入子模块、定义__all__等:

# my_package/__init__.py
from .module_a import func_a
from .module_b import func_b

__all__ = ['func_a', 'func_b']
1
2
3
4
5

__all__定义了from my_package import *会导入哪些名字。

Q2:import *会导入所有名字吗?

如果模块定义了__all__,只导入__all__中的名字;否则导入所有不以_开头的名字。

Q3:可以导入.pyc文件吗?

可以。Python会优先使用.pyc字节码文件(在__pycache__目录中),避免重复编译。

Q4:为什么不推荐修改sys.path?

修改sys.path会影响全局导入行为,降低代码可移植性。更好的做法是用虚拟环境、正确的包结构或安装包到site-packages。

Q5:如何查看模块的搜索路径?

import module_name
print(module_name.__file__)  # 模块文件路径
1
2

对于内置模块,__file__可能不存在。

← 什么是闭包?闭包中的变量为什么会出现延迟绑定? Python中的类和对象是什么? →

评论

验证登录状态...

You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.