模块和包 ¶
在 Python 中,模块(Module) 和 包(Package) 是代码组织的基本单元,它们帮助开发者将代码逻辑分层、复用和管理。
1.模块 ¶
- 模块(Module) 是一个包含 Python 代码的
.py
文件,例如math_utils.py
。 - 模块中可以定义变量、函数、类,以及可执行代码。
- 模块的目的是将相关功能封装在一起,方便复用。
定义模块:
# 文件名:math_utils.py
"""一个数学工具模块(模块的文档字符串)"""
def add(a: int, b: int) -> int:
"""返回两个数的和"""
return a + b
PI = 3.14159 # 模块级变量
class Circle:
def __init__(self, radius: float):
self.radius = radius
def area(self) -> float:
return PI * self.radius**2
通过 import
关键字导入模块。Python的模块名就是不包含.py后缀的文件名,比如上例的文件math_utils.py
,那么模块的名称就是math_utils
。 Python通过查找当前目录和已经安装的包所在的目录来寻找模块。
# 导入整个模块
import math_utils
print(math_utils.add(2, 3)) # 输出 5
c = math_utils.Circle(5)
print(c.area()) # 输出 78.53975
# 导入特定内容(推荐避免命名冲突)
from math_utils import add, PI
print(add(3, 4)) # 输出 7
常用模块属性:
(1) __name__
:模块的名称。当直接运行模块时,__name__
为 "__main__"
;当被导入时,为模块的文件名。
__name__
在模块加载后是只读的,强行修改可能导致不可预测行为。
(2) __file__
:模块的文件路径。
(3) __doc__
:模块的文档字符串(即文件开头的多行注释)。
(4) __package__
:表示模块所属的包名。对相对导入(如 from .. import module
)非常重要。
- 如果模块是包(目录),则
__package__
为包名。 - 如果模块是顶层模块(不在包中),则
__package__
为None
或空字符串。
# my_package/submodule.py
print(__package__) # 输出 "my_package"
(5) __loader__
:表示加载该模块的 加载器对象(如 SourceFileLoader
),负责从文件或资源中加载模块。
__loader__
在模块加载后是只读的,强行修改可能导致不可预测行为。
import math
print(math.__loader__)
# <class '_frozen_importlib.BuiltinImporter'>
(6) __spec__
:存储模块的 导入规范(importlib.machinery.ModuleSpec
对象),包含模块的导入路径、加载器等元数据。
- 高级模块操作,如在动态导入或元编程中,
__spec__
用于重构模块导入行为。
import math
print(math.__spec__.name)
# 输出 math
print(math.__spec__.origin)
# 输出 built-in
(7) __path__
:仅对 包 有效(即目录型模块),表示包内搜索子模块的路径列表。
- 包可以通过修改
__path__
扩展子模块搜索路径(如插件系统)。
# my_package/__init__.py
print(__path__) # 输出包路径列表,如 ['/path/to/my_package']
(8) __dict__
:模块的全局命名空间(字典形式),包含模块中定义的所有变量、函数、类等,动态查看或修改模块属性。
- 避免直接修改
__dict__
:虽然可以动态修改模块属性(如module.__dict__['key'] = value
),但可能导致代码可维护性下降。
import math_utils
print(math_utils.__dict__.keys())
# dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__file__', '__cached__', '__builtins__', 'add', 'PI', 'Circle'])
# 动态添加属性(等同于 math_utils.new_var = 100)
math_utils.__dict__['new_var'] = 100
(9) __builtins__
:引用 Python 的 内置命名空间(如内置函数 len
、异常 Exception
等)。在模块中默认存在,但在函数或类中需要显式访问。
print(__builtins__.len([1, 2, 3])) # 输出 3
(10) __annotations__
:存储模块中 变量的类型提示(字典形式,键为变量名,值为类型)。
# math_utils.py
PI: float = 3.14159
print(__annotations__) # 输出 {'PI': <class 'float'>}
(11) __version__
: 非内置属性,许多第三方模块自定义此属性表示版本号(如 numpy.__version__
)。
import numpy
print(numpy.__version__) # 输出 2.2.1
(12) __author__
或 __license__
:非内置属性,开发者自定义的模块作者或协议信息。
# my_module.py
__author__ = "Alice"
__license__ = "MIT"
属性示例代码:
# 示例模块:demo_module.py
"""模块的文档字符串(__doc__)"""
import sys
VERSION: str = "1.0" # 类型提示(会记录到 __annotations__)
__author__ = "Alice" # 自定义属性
__license__ = "MIT" # 自定义属性
def add(a: int, b: int) -> int:
return a + b
if __name__ == "__main__":
print("直接运行模块时执行")
# 查看属性
print("__name__:", __name__)
print("__file__:", __file__)
print("__package__:", __package__)
print("__annotations__:", __annotations__)
print("__loader__:", type(__loader__).__name__)
print("__spec__:", __spec__.name if __spec__ else None)
# 使用 globals() 获取全局变量字典,再访问其 __dict__ 属性
print("__dict__ keys:", list(globals().keys())[:3])
# 使用 sys.modules[__name__] 获取当前模块对象,再访问其 __dict__ 属性
current_module = sys.modules[__name__]
print("__dict__ keys:", list(current_module.__dict__.keys())[:3])
# 输出结果:
# 直接运行模块时执行
# __name__: __main__
# __file__: /opt/mySite/demo_model.py
# __package__: None
# __annotations__: {'VERSION': <class 'str'>}
# __loader__: SourceFileLoader
# __spec__: None
# __dict__ keys: ['__name__', '__doc__', '__package__']
在上面代码中,直接使用 print("__dict__ keys:", list(__dict__.keys())[:3])
会报错:NameError: name '__dict__' is not defined.
。这是因为__dict__
是模块对象的一个属性,表示模块的全局命名空间(字典形式)。但在模块代码中,直接写 __dict__
会被 Python 解释器视为一个普通变量名(而非模块属性),从而触发 NameError。
2.包 ¶
包(Package)是一个包含多个模块的目录,必须包含一个 __init__.py
文件(Python 3.3+ 后可以隐式存在,但显式保留更规范)。
包用于组织更复杂的项目结构,例如分层架构。在创建包的层级结构时,Python社区一般建议:扁平结构优于层级结构。
创建包:
my_package/
├── __init__.py # 包的初始化文件(可以为空)
├── math_utils.py # 模块1
└── string_utils.py # 模块2
包的 __init__.py
:
- 在包被导入时,
__init__.py
会自动执行。 - 可以定义包的公共接口或初始化逻辑。
- 通过
__all__
变量指定from package import *
时导入的模块列表。
# my_package/__init__.py
__all__ = ["math_utils", "string_utils"] # 控制导入范围
# 初始化代码(可选)
print("my_package 初始化完成!")
使用包:
# 导入包中的模块
import my_package.math_utils
my_package.math_utils.add(1, 2)
# 从包中导入模块
from my_package import string_utils
string_utils.reverse("hello")
# 从子包导入(假设有嵌套包)
from my_package.subpackage import module
3.导入方式 ¶
绝对导入:
- 从项目根目录开始的完整路径导入。
- 推荐在复杂项目中使用。
from my_package.subpackage.module import func
相对导入:
- 在包内部使用,通过
.
表示当前目录,..
表示上级目录。 - 只能在包内的模块中使用。
# 在 my_package/subpackage/module.py 中导入同级模块
from . import sibling_module
# 在子包中导入父包模块
from ..math_utils import add
建议:
1.避免循环导入
- 问题:模块 A 导入模块 B,模块 B 又导入模块 A,导致死锁。
- 解决:重构代码,将公共逻辑提取到第三个模块。
2.模块搜索路径
- Python 在导入模块时,会按
sys.path
中的路径顺序查找。 - 自定义模块需确保路径在
sys.path
中(可通过sys.path.append()
添加)。
3.if __name__ == "__main__"
- 当模块作为脚本直接运行时,
__name__
为"__main__"
。 - 用于编写模块的测试代码,避免在被导入时执行。
# math_utils.py
def add(a, b):
return a + b
if __name__ == "__main__":
# 直接运行模块时执行
print(add(2, 3)) # 测试代码
4.包的命名规范
- 包名应全小写,避免使用特殊字符。
- 使用下划线分隔单词(如
data_utils
)。
5.动态导入
- 使用
importlib
动态导入模块(一般不推荐,除非必要)。
import importlib
module_name = "math_utils"
math_utils = importlib.import_module(module_name)
总结:
- 模块:单个
.py
文件,封装变量、函数、类。 - 包:包含
__init__.py
的目录,用于组织多个模块。 - 导入方式:绝对导入(推荐)、相对导入(包内部使用)。
- 最佳实践:
- 避免循环导入。
- 使用
if __name__ == "__main__"
隔离测试代码。 - 合理划分模块和包,保持代码可维护性。
4.项目中的典型结构 ¶
project/
├── src/
│ ├── my_package/
│ │ ├── __init__.py
│ │ ├── math_utils.py
│ │ └── data/
│ │ ├── __init__.py
│ │ └── loader.py
│ └── scripts/
│ └── main.py
├── tests/
│ ├── test_math.py
│ └── test_data.py
├── requirements.txt
└── README.md
5.特殊模块 ¶
Python 中有一些**特殊模块**,它们不是通过 pip
安装的第三方包,而是由 Python 解释器内置的模块,用于提供特定的功能或元信息。它们通常以双下划线开头和结尾(如 __future__
、__main__
),并在 Python 的运行时环境中扮演重要角色。
5.1.__future__
¶
- 启用未来版本的 Python 特性。
from __future__ import annotations
5.2.__main__
¶
- 表示当前运行的脚本模块。当直接运行一个 Python 文件时,
__name__
会被设置为"__main__"
。
if __name__ == "__main__":
print("This script is being run directly.")
5.3.__builtins__
¶
- 包含 Python 的内置函数和对象(如
print
、len
、Exception
等)。
print(__builtins__.len([1, 2, 3])) # 输出 3
5.4.__annotations__
¶
- 存储模块、类或函数中的类型注解(Python 3.7+)。
def greet(name: str) -> str:
return f"Hello, {name}"
print(greet.__annotations__) # 输出 {'name': <class 'str'>, 'return': <class 'str'>}
5.5.__import__
¶
- Python 的底层导入机制,
import
语句的实际实现。
math = __import__("math")
print(math.sqrt(16)) # 输出 4.0
5.6. __package__
¶
- 表示当前模块所属的包名(用于相对导入)。
print(__package__) # 输出当前模块的包名(如 "my_package")
5.7.__file__
¶
- 表示当前模块的文件路径。
print(__file__) # 输出当前模块的文件路径(如 "/path/to/module.py")
5.8.__path__
¶
- 表示包的搜索路径(仅对包有效)。
print(__path__) # 输出包的路径列表(如 ["/path/to/my_package"])
5.9.__spec__
¶
- 存储模块的导入规范(
importlib.machinery.ModuleSpec
对象,Python 3.4+)。
import math
print(math.__spec__.name) # 输出 "math"
5.10.__loader__
¶
- 表示加载当前模块的加载器对象。
import math
print(math.__loader__) # 输出加载器对象(如 <_frozen_importlib_external.SourceFileLoader>)
5.11.__debug__
¶
- 表示 Python 是否运行在调试模式(
-O
选项会将其设置为False
)。
if __debug__:
print("Debug mode is enabled.")
else:
print("Debug mode is disabled.")
5.12.__cached__
¶
- 表示模块的缓存文件路径(
.pyc
文件)。
print(__cached__) # 输出缓存文件路径(如 "/path/to/__pycache__/module.cpython-38.pyc")
5.13.__doc__
¶
- 存储模块、类或函数的文档字符串(docstring)。
def greet():
"""This is a docstring."""
pass
print(greet.__doc__) # 输出 "This is a docstring."
5.14.__name__
¶
- 表示当前模块的名称。当模块作为脚本运行时,
__name__
为"__main__"
。
print(__name__) # 输出当前模块的名称(如 "__main__" 或模块名)
5.15. __all__
¶
- 定义模块的公共接口(控制
from module import *
的行为)。 from module import *
只会导入func1
和func2
。
__all__ = ["func1", "func2"]
def func1():
pass
def func2():
pass
def func3():
pass