Skip to content

Python 3 - 模块

模块是 Python 中组织代码的基本概念。它们允许你将相关的函数、类和变量分组到单独的文件中,使你的代码更易于管理、重用和理解。

本质上,模块就是一个包含 Python 定义和语句的 Python 文件(.py)。文件名(不带 .py 扩展名)就成为模块名。

让我们创建一个名为 greetings.py 的模块,其中包含一个函数:

greetings.py
def say_hello(name):
"""打印一条问候消息。"""
print(f"Hello, {name}!")
# You could also define variables or classes here
PI = 3.14159

要在另一个 Python 文件(或交互式解释器)中使用模块中的代码,你需要使用 import 语句。

import module_name

当 Python 遇到 import greetings 时,它会在一个特定的目录列表中(模块搜索路径)查找 greetings.py。如果找到,Python 将执行模块中的代码(如果存在任何顶级代码),并通过模块名使其内容可用。

main_script.py
import greetings # 导入 greetings 模块
# 使用点表示法访问函数/变量
greetings.say_hello("Alice")
print(f"The value of PI from greetings module is: {greetings.PI}")

运行 main_script.py 会产生以下输出:

Hello, Alice!
The value of PI from greetings module is: 3.14159

一个模块在每个 Python 会话中只会被加载和执行 一次,即使它被导入多次。这可以防止重复初始化。

或者,你可以使用 from...import 将特定名称(函数、类、变量)直接导入到当前脚本的命名空间中。

from module_name import name1, name2

示例:只导入 say_hello 函数:

main_script_from.py
from greetings import say_hello
# 现在直接调用函数
say_hello("Bob")
# PI 没有直接导入,因此这里会引发错误:
# print(PI)

这使得代码稍微短一些,但如果导入的名称在你的脚本中已经存在,可能会导致命名冲突。

你可以使用以下方式导入模块中所有公共名称(那些不以下划线 _ 开头的名称):

from module_name import *

警告:在生产代码中通常不建议这样做。它会将可能很多名称导入到你的当前命名空间,使得难以确定特定函数或变量的来源,并增加了名称冲突的风险。请谨慎使用,也许只在交互式会话中为了方便而使用。

将模块作为脚本执行 (if __name__ == "__main__":)

Section titled “将模块作为脚本执行 (if __name__ == "__main__":)”

每个 Python 模块都有一个内置变量 __name__。当模块作为主脚本直接运行时(例如 python my_module.py),其 __name__ 被设置为字符串 "__main__"。当模块被另一个脚本导入时,其 __name__ 被设置为模块的实际名称(例如 "my_module")。

这使得你可以在模块中编写仅在模块直接运行时执行的代码,通常用于测试、示例或使模块可运行。

greetings.py
def say_hello(name):
"""打印一条问候消息。"""
print(f"Hello, {name}!")
PI = 3.14159
# 这个块 ONLY 在 greetings.py 直接执行时运行
if __name__ == "__main__":
print("将 greetings.py 作为主脚本运行。")
say_hello("直接运行用户")
print(f"PI 值为: {PI}")

如果你运行 python greetings.py,你会看到 if 块中的输出。如果你将 greetings 导入到另一个脚本中,该块将不会自动执行。

当你 import my_module 时,Python 按以下顺序搜索 my_module.py(或名为 my_module 的包):

  • 包含输入脚本的目录(如果是交互式会话,则是当前目录)。
  • PYTHONPATH 环境变量中列出的目录(如果设置)。
  • 安装相关的默认路径(例如,Python 安装目录或虚拟环境内的 .../site-packages)。

完整的搜索路径以字符串列表的形式存储在 sys.path 中(你需要先 import sys)。

最佳实践:对于管理项目特定的依赖项,强烈建议使用虚拟环境(如 venv 或 conda)。虚拟环境创建隔离的 Python 环境,你可以在其中安装包而不会影响你的全局 Python 安装或其他项目。这在很大程度上取代了在典型开发中手动管理 PYTHONPATH 的需求。

命名空间是从名称到对象(如变量、函数)的映射。模块创建自己的全局命名空间。当你访问 greetings.say_hello 时,你是在与 greetings 模块关联的命名空间中查找 say_hello。

函数调用创建局部命名空间。Python 遵循 LEGB 规则来查找名称:局部 (Local) -> 外部函数局部 (Enclosing function locals) -> 全局 (Global - 模块级别) -> 内置 (Built-in)。

global 关键字在函数内部使用,表示赋值应该修改全局变量,而不是创建新的局部变量。nonlocal 关键字(用于嵌套函数)在外部函数作用域中的名称上做类似的操作。

#!/usr/bin/env python3
money = 2000 # 全局变量
def add_money():
# 要修改 *全局* 变量 money,请取消下一行的注释:
# global money
money = 1 # 如果没有使用 'global',则创建一个 *局部* 变量 'money'
money = money + 1
print("函数内部 (局部 money):", money)
print("调用函数之前 (全局 money):", money)
add_money()
print("调用函数之后 (全局 money):", money) # 如果没有使用 'global',则未改变

内置函数 dir() 在对模块对象调用时,返回该模块内定义的名称的排序列表。

#!/usr/bin/env python3
import math
content = dir(math)
print(content)
# 尝试我们的模块
import greetings
print("\n greetings 模块的内容:")
print(dir(greetings))

这对于探索模块的内容很有用。它列出了函数、类、变量以及特殊属性,如 __name__ 和 __file__。

如前所述,模块通常只加载一次。如果在主脚本运行时修改了模块的源文件(在交互式开发或长时间运行的应用程序中常见),更改不会自动反映出来。

在 Python 3 中,reload() 函数从内置函数移到了 importlib 模块。你可以使用它来重新执行模块代码并更新其命名空间。

import importlib
import greetings
# ... 修改 greetings.py ...
# 重新加载模块
importlib.reload(greetings)
# 现在调用将使用 greetings.py 中更新的代码
greetings.say_hello("After Reload")

注意:reload() 主要用于交互式调试会话。在生产代码中依赖它通常表明设计存在问题。通常最好重新启动应用程序或设计动态配置加载机制。

对于大型项目,你可能希望将相关的模块组织成目录层次结构。这就是包的作用。包就是一个包含 Python 模块和特殊(通常为空)文件 __init__.py 的目录。

__init__.py 文件向 Python 表明该目录应该被视为一个包。它也可以包含包的初始化代码,或者定义在使用 from package import * 时导出的符号(通过 __all__ 列表)。

考虑以下结构:

my_app/
__init__.py
main.py
utils/
__init__.py
strings.py
math_ops.py
models/
__init__.py
user.py

这里,my_app、utils 和 models 是包,因为它们包含 __init__.py。你可以使用点表示法导入模块:

# 在 my_app/main.py 中
import my_app.utils.strings # 方法 1:完整路径
from my_app.utils import math_ops # 方法 2:导入子模块
from my_app.models.user import User # 方法 3:导入特定类/函数
# 使用导入
cleaned_text = my_app.utils.strings.clean(" some text ")
result = math_ops.add(5, 3)
new_user = User("admin")
# --- 相对导入 (包内部) ---
# 在 my_app/utils/strings.py 中,你可能导入 math_ops:
# from . import math_ops # '.' 表示当前包 ('utils')
# from .. import main # '..' 表示父包 ('my_app')

包允许你通过分组相关功能和避免命名冲突来创建结构良好、大型的应用程序。