模块与包
# Python 模块与包
随着程序规模的增长,将所有代码都放在一个文件中是不可行且难以维护的。Python 通过模块 (Modules) 和包 (Packages) 的机制来组织代码,使其结构清晰、易于复用和管理。
# 1. 模块 (Module)
在 Python 中,任何一个 .py
文件都可以被看作一个模块。模块中可以包含变量、函数、类等定义。通过将相关的代码组织在不同的模块中,我们可以实现代码的逻辑分离。
# 1.1 为什么使用模块?
- 代码组织: 将复杂的代码分解成多个逻辑上独立的单元。
- 可复用性: 一个模块可以被多个不同的程序导入和使用。
- 命名空间: 每个模块都有自己的命名空间,可以有效避免不同模块间的命名冲突。
# 1.2 创建与使用模块
假设我们有一个 my_math.py
文件:
# my_math.py
PI = 3.14159
def add(a, b):
"""返回两数之和"""
return a + b
class Circle:
"""圆形类"""
def __init__(self, radius):
self.radius = radius
def area(self):
return PI * self.radius * self.radius
2
3
4
5
6
7
8
9
10
11
12
13
14
15
现在,我们可以在另一个文件 main.py
中使用这个模块。
# 1.3 import
语句
这是最直接的导入方式,它会导入整个模块。
# main.py
import my_math
# 使用时需要通过 "模块名.成员" 的方式访问
print(f"PI 的值: {my_math.PI}")
circle = my_math.Circle(10)
print(f"半径为10的圆面积: {circle.area()}")
2
3
4
5
6
7
# 1.4 from ... import
语句
如果你只想从模块中导入特定的成员,可以使用 from...import
。
# main.py
from my_math import PI, add
# 可以直接使用导入的成员,无需模块名前缀
print(f"PI 的值: {PI}")
print(f"5 + 3 = {add(5, 3)}")
2
3
4
5
6
# 1.5 as
关键字:别名
当模块名很长或者你想避免命名冲突时,可以使用 as
来给导入的模块或成员起一个别名。
import my_math as m
from my_math import Circle as C
print(f"PI: {m.PI}")
my_circle = C(5)
2
3
4
5
# 1.6 from ... import *
的行为
使用 *
可以导入模块中所有不以下划线 _
开头的公开名称。
from my_math import *
# PI, add, Circle 都可以直接使用
print(PI)
2
3
4
警告:通常不推荐在代码中使用 from module import *
,因为它会将所有导入的名称直接放入当前命名空间,很容易导致意想不到的命名冲突,降低代码的可读性。
# 1.7 控制 import *
的行为: __all__
你可以在模块中定义一个名为 __all__
的列表,来明确指定当使用 from my_module import *
时,哪些名称应该被导入。
# my_math_pro.py
__all__ = ['add', 'PI'] # 只希望外部通过 * 导入 add 和 PI
PI = 3.14159
_INTERNAL_CONSTANT = 9.8 # 以下划线开头,默认不会被 * 导入
def add(a, b):
return a + b
class Circle: # Circle 没有在 __all__ 中
pass
2
3
4
5
6
7
8
9
10
11
12
# main.py
from my_math_pro import *
print(add(1, 2)) # 正常
print(PI) # 正常
# c = Circle() # NameError: name 'Circle' is not defined
2
3
4
5
6
# 1.8 模块的缓存与重新加载
为了提高效率,Python 会缓存已经导入的模块。当你第二次 import
同一个模块时,Python 不会再次执行模块文件,而是直接从缓存(sys.modules
)中获取。
这在开发和调试时可能会带来问题。如果你修改了模块的代码,需要重新加载它才能看到变化。可以使用 importlib
模块来实现。
import my_math
import importlib
# ... 假设你在这里修改了 my_math.py 的源文件 ...
# 重新加载模块
importlib.reload(my_math)
2
3
4
5
6
7
# 1.9 模块的搜索路径
当你 import
一个模块时,Python 解释器会按照以下顺序在 sys.path
列表中指定的目录里搜索它:
- 当前目录: 运行主程序的脚本所在的目录。
PYTHONPATH
环境变量: 一个包含目录列表的环境变量,可以手动设置。- 标准库目录: Python 安装时自带的库所在的目录。
import sys
print(sys.path)
2
# 2. 包 (Package)
当项目变得更大,拥有大量模块时,我们可以使用包来进一步组织它们。
包就是一个包含 __init__.py
文件的目录。这个目录里可以存放其他的模块文件或子包。
__init__.py
文件的作用:- 标识目录为包: 它的存在告诉 Python 这个目录应该被当作一个包来对待。在现代Python(3.3+)中,即使没有
__init__.py
文件,目录也可以被当作“命名空间包”导入,但为了兼容性和清晰性,创建它是最佳实践。 - 初始化包: 当包被导入时,
__init__.py
文件会被自动执行。你可以在这里进行包级别的初始化操作,或者定义包的__all__
变量。
- 标识目录为包: 它的存在告诉 Python 这个目录应该被当作一个包来对待。在现代Python(3.3+)中,即使没有
# 2.1 创建与组织包
假设我们有如下的目录结构:
my_project/
├── main.py
└── my_app/
├── __init__.py
├── api.py
└── models/
├── __init__.py
└── user.py
2
3
4
5
6
7
8
my_app
是一个顶级包。models
是my_app
的一个子包。api.py
和user.py
是模块。
# 2.2 在包外部导入
在 main.py
中,我们可以这样导入包中的模块:
# main.py
# 导入 my_app 包中的 api 模块
import my_app.api
my_app.api.call_api()
# 导入 user 模块,并起别名
from my_app.models import user as user_model
db_user = user_model.User("Alice")
2
3
4
5
6
7
8
9
# 2.3 __init__.py
的妙用:提升成员
你可以在 __init__.py
中将包内深层次的成员提升到包的顶层,方便外部调用。
# 在 my_app/models/__init__.py 中写入:
from .user import User
# 那么在 main.py 中就可以这样导入,显得更简洁:
from my_app.models import User # 而不是 from my_app.models.user import User
user_instance = User("Bob")
2
3
4
5
6
# 2.4 包内导入:绝对与相对导入
当你在一个包的内部,需要导入同包的其他模块时,有两种方式:
绝对导入 (Absolute Import): 从项目的根目录(即包含
main.py
的my_project
目录)开始,写出完整的路径。# 假设在 my_app/api.py 中,需要使用 User 模型 from my_app.models.user import User # 绝对路径导入 def get_user(id): # ... return User("test")
1
2
3
4
5
6优点: 路径清晰,明确,无论当前文件被移动到何处,导入路径都有效。 缺点: 如果顶层包名(
my_app
)改变,所有导入语句都需要修改。相对导入 (Relative Import): 使用
.
和..
来表示当前路径和父路径,只能在包内部使用。.
: 表示当前包(目录)。..
: 表示上级包(目录)。
# 假设在 my_app/api.py 中,需要使用同级目录下的 models 子包中的 user 模块 from .models.user import User # .models 表示从当前包(my_app)寻找 models # 假设在 my_app/models/user.py 中,需要导入 my_app/api.py from ..api import get_user # `..` 表示 models 的上级目录,即 my_app
1
2
3
4
5优点: 当整个包被重命名或移动时,内部引用不受影响。 缺点: 增加了路径的复杂性,过度使用
..
会使代码难以理解。
# 3. if __name__ == "__main__"
的作用
这是一个非常常见的代码块,它的核心作用是:让一个模块既可以作为脚本被直接运行,也可以作为模块被其他程序导入。
- 当一个
.py
文件被直接执行时 (例如python my_module.py
),Python 解释器会将其内置的__name__
变量设置为字符串"__main__"
。 - 当一个
.py
文件被作为模块导入时 (import my_module
),它的__name__
变量会被设置为模块自己的名字 (例如"my_module"
)。
# my_math.py
PI = 3.14
def add(a, b):
return a + b
def main_test():
"""包含测试或演示代码的函数"""
print("--- 模块自测 ---")
print(f"计算 5 + 10: {add(5, 10)}")
print("--- 自测结束 ---")
# 检查这个文件是否被直接运行
if __name__ == "__main__":
main_test()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 运行
python my_math.py
:__name__
是"__main__"
,所以main_test()
函数会被调用,屏幕上会打印出演示信息。 - 在其他文件中
import my_math
:__name__
是"my_math"
,if
条件不满足,main_test()
函数不会被调用。这样,导入my_math
只会得到PI
和add
,而不会执行测试代码,非常干净。