Skip to content

模块和包

在 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 的内置函数和对象(如 printlenException 等)。
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 * 只会导入 func1func2
__all__ = ["func1", "func2"]

def func1():
    pass

def func2():
    pass

def func3():
    pass