# 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
2
3
4
5
导入模块:
import math_utils
print(math_utils.add(2, 3)) # 5
print(math_utils.PI) # 3.14159
2
3
4
# 包
包是包含__init__.py的目录,用于组织多个模块:
my_package/
__init__.py
module_a.py
module_b.py
2
3
4
导入包中的模块:
import my_package.module_a
from my_package import module_b
my_package.module_a.func()
module_b.func()
2
3
4
5
__init__.py可以是空文件,也可以包含包的初始化代码。Python 3.3+支持隐式命名空间包(没有__init__.py),但显式包仍然是推荐做法。
# import的查找顺序
import module_name时,Python按以下顺序查找:
sys.modules缓存:已导入的模块直接返回。- 内置模块:如
sys、os、math。 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', ...]
2
3
可以动态修改sys.path,但不推荐:
sys.path.append('/custom/path')
import custom_module
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 *
2
3
4
5
6
7
8
9
10
11
12
13
from math import *会污染命名空间,隐藏了名字来源,降低可读性。

# 模块缓存sys.modules
模块只会被导入一次,后续import直接从缓存返回:
# first.py
print("first.py is being imported")
# main.py
import first # first.py is being imported
import first # 不会再次打印,直接从缓存返回
2
3
4
5
6
可以查看缓存:
import sys
import math
print('math' in sys.modules) # True
print(sys.modules['math']) # <module 'math' from '...'>
2
3
4
5
强制重新加载模块(不推荐):
import importlib
importlib.reload(math)
2
# 相对导入与绝对导入
绝对导入:从sys.path根开始查找:
from my_package.module_a import func
相对导入:从当前包的位置开始查找:
# my_package/module_b.py
from . import module_a # 导入同级模块
from .module_a import func # 导入同级模块中的名字
from .. import parent_module # 导入上级包中的模块
2
3
4
相对导入只能在包内使用,不能在顶层脚本中使用。
# 循环导入问题
两个模块互相导入会导致循环导入:
# a.py
import b
def func_a():
b.func_b()
# b.py
import a
def func_b():
a.func_a()
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
2
3
4
5
6
7
导入a时,a导入b,b导入a(此时a还没执行完),b执行完后返回a,a继续执行print(b.var)。此时b已经完成,b.var存在,不会出错。但如果a在导入时就访问b的名字,而b还没定义,就会出错。
解决方法:
- 延迟导入:在函数内导入。
- 重构代码:把共同依赖提取到第三个模块。
- 导入模块而不是名字:
import b而不是from b import func。
# __name__变量
每个模块有__name__变量,表示模块名:
# module.py
print(__name__) # 'module'
# main.py
import module # 输出 'module'
2
3
4
5
当模块作为脚本直接运行时,__name__是'__main__':
# script.py
if __name__ == '__main__':
print("This is the main script")
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']
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__) # 模块文件路径
2
对于内置模块,__file__可能不存在。
评论
验证登录状态...