Skip to content

18|模块与包

Python 代码写在 .py 文件里,一个文件就是一个模块。模块把相关代码组织在一起,可以被其他文件导入复用。多个模块按目录组织,形成包。

一、模块

math.py

python
"""数学工具模块。"""

PI = 3.14159


def circle_area(radius):
    """计算圆的面积。"""
    return PI * radius ** 2


def circle_perimeter(radius):
    """计算圆的周长。"""
    return 2 * PI * radius

另一个文件导入使用:

python
import math

print(math.PI)
print(math.circle_area(5))

注意:这里导入的是当前目录下的 math.py,不是 Python 标准库的 math 模块。如果当前目录和标准库有同名模块,当前目录的优先——这是一个常见的命名冲突陷阱。模块名不要和标准库冲突(如 math.pysys.pyjson.py)。

二、导入方式

python
import math                    # 导入整个模块,用 math.xxx 访问
import math as m               # 起别名,用 m.xxx 访问
from math import circle_area   # 只导入指定函数,直接用 circle_area
from math import *             # 导入所有公开名称(不推荐)

from math import circle_area 可以直接用函数名,但失去了模块前缀,如果多个模块有同名函数会冲突。

from math import * 导入模块中 __all__ 列表定义的所有名称。如果没有 __all__,则导入所有不以 _ 开头的名称。这种写法让代码的依赖关系不清晰——看到 circle_area() 不知道它来自哪个模块。除非在交互式环境里快速测试,否则不推荐。

三、模块搜索路径

导入模块时,Python 按以下顺序查找:

  1. 当前目录
  2. PYTHONPATH 环境变量指定的目录
  3. 标准库目录
  4. 第三方包安装目录(site-packages)

查看搜索路径:

python
import sys
print(sys.path)

四、if __name__ == "__main__"

模块既可以被导入,也可以直接执行。if __name__ == "__main__" 区分这两种场景:

python
# utils.py

def helper():
    return "help"

if __name__ == "__main__":
    print("utils.py 被直接执行")
    print(helper())
python
# main.py
import utils

print(utils.helper())   # 输出 help
# 不会输出 "utils.py 被直接执行"

被导入时,__name__ 的值是模块名 "utils"。直接执行时,__name__ 的值是 "__main__"

这个机制让模块既能提供函数给别人用,又能包含自己的测试代码。

五、包

包是包含多个模块的目录,目录下必须有 __init__.py 文件(Python 3.3+ 非必须,但建议保留)。

text
myproject/
├── main.py
└── utils/
    ├── __init__.py
    ├── math_tools.py
    └── string_tools.py

__init__.py 可以是空文件,也可以包含包的初始化代码:

python
# utils/__init__.py
from .math_tools import circle_area
from .string_tools import normalize

这样导入包时,这些名称自动可用:

python
from utils import circle_area, normalize

相对导入

包内部模块之间的导入:

python
# utils/string_tools.py
from .math_tools import PI    # 同一包内的模块
from ..config import settings  # 上级目录的模块(需要是包结构)

相对导入用 . 表示当前包,.. 表示上级包。相对导入只能在包内部使用,直接运行文件时会报错。

六、__all__

控制 from module import * 导入哪些名称:

python
# utils/math_tools.py
__all__ = ["circle_area", "circle_perimeter"]

PI = 3.14159   # 不会被 import * 导入

def circle_area(radius): ...
def circle_perimeter(radius): ...

没有 __all__ 时,import * 导入所有不以 _ 开头的名称。有 __all__ 时,只导入列表中指定的名称。

七、重新加载模块

模块导入后会被缓存。修改模块文件后,已导入的模块不会自动更新:

python
import importlib
import utils

# 修改 utils.py 后重新加载
importlib.reload(utils)

这个操作在交互式开发和调试时有用。生产代码中很少需要重新加载模块。

记忆锚点:一个 .py 文件是一个模块;import 导入,from...import 导入指定名称;不要和标准库模块同名;if __name__ == "__main__" 区分导入和直接执行;包是带 __init__.py 的目录;__all__ 控制 import * 的行为;模块导入后缓存,修改后需 importlib.reload()