模块导入
模块是包含 Python 定义和语句的 .py 文件。当程序规模增长时,将代码拆分到不同模块中可以避免重复编写,也便于维护。模块中的函数、类和变量可以通过 import 机制被其他脚本复用。
import 语句的多种形式
最基本的导入方式是 import 模块名,它把模块对象绑定到当前命名空间的一个变量上,模块内部的所有名称都保留在该模块自己的命名空间中:
# 假设 mathdemo.py 中定义了 add 和 PI
import mathdemo
print(mathdemo.PI) # 3.14159
print(mathdemo.add(2, 3)) # 5
这种写法的好处是命名空间隔离——模块中的全局变量不会与用户代码中的同名变量冲突。模块作者可以放心使用 PI、add 这类常见名称,因为调用者必须通过 mathdemo.PI 才能访问到它。
如果某个函数使用频繁,可以把它赋值给局部变量,但这只是创建了一个指向原函数的引用,并不会复制函数本身:
from mathdemo import add
my_add = add
my_add(10, 20) # 30
from...import 与命名空间注入
from 模块 import 名称 的语义是:找到指定模块,执行其顶层语句(如果是首次导入),然后把被导入的名称直接放入当前模块的命名空间。此后无需前缀即可使用:
from mathdemo import add, PI
print(add(1, PI)) # 4.14159
注意,这种写法不会把模块名本身引入当前命名空间。下面的代码会报错:
from mathdemo import add
print(mathdemo.add(2, 3)) # NameError: name 'mathdemo' is not defined
from 模块 import * 会导入模块中所有不以下划线开头的名称。它本质上是一种批量 from...import,但风险在于可能覆盖当前命名空间中已有的同名对象:
# 当前模块已定义了自己的 add 函数
def add(a, b, c):
return a + b + c
from mathdemo import * # mathdemo.add 覆盖了上面的 add
print(add(1, 2)) # TypeError: add() missing 1 required positional argument
因此,在生产代码中应避免使用 import *,除非在交互式会话中为了少敲几次键盘。
as 别名:解决名称冲突与简化书写
当模块名过长,或者两个模块提供了同名对象时,as 子句可以创建别名。import ... as 给模块本身起别名:
import json
import mypackage.serializer.json as json_compat # 与标准库 json 同名时避免冲突
data = json.loads('{"a": 1}')
legacy = json_compat.parse_old_format('a=1') # 调用自定义模块
from ... import ... as 则给导入的单个对象起别名:
from mathdemo import add as math_add
from string import capwords as title_case
print(math_add(5, 7)) # 12
print(title_case("hello world")) # "Hello World"
别名在科学计算领域尤为常见,例如 import numpy as np、import pandas as pd,这已经成为社区约定俗成的写法。
导入的执行机制与缓存
模块中的可执行语句(包括函数定义、类定义和顶层表达式)在首次遇到 import 时执行一次。函数定义的执行效果是把函数名加入模块的全局命名空间;类定义同理。如果同一个模块被再次导入,Python 会直接返回已缓存的模块对象,不会重新执行文件内容:
# counter.py
print("counter 模块被加载了")
count = 0
def increment():
global count
count += 1
return count
在交互式会话中测试:
>>> import counter
counter 模块被加载了
>>> import counter # 再次导入,无输出
>>> counter.increment()
1
>>> counter.increment()
2
这种单次执行的设计保证了模块级状态(如全局变量 count)在多次导入间保持一致。但也带来一个问题:如果你在编辑器中修改了 counter.py,已运行的解释器不会自动感知变化。此时需要重启解释器,或者在交互式会话中使用 importlib.reload:
>>> import importlib
>>> importlib.reload(counter)
counter 模块被加载了
<module 'counter' from 'counter.py'>
reload 会重新执行模块代码,但有一个重要细节:已经通过 from counter import increment 获取到 increment 引用的代码,仍然指向旧函数对象;只有通过 counter.increment 访问的调用方才会使用新定义。
name 与双用途模块
每个模块都有一个全局变量 __name__,其值为模块名字符串。当文件被直接作为脚本运行时,__name__ 被特殊地设置为 "__main__";而被导入时,__name__ 就是文件名去掉 .py 后缀后的名称。利用这一性质,可以在模块末尾放置测试代码或命令行接口,使其仅在直接运行时生效:
# fibo.py
def fib(n):
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a + b
print()
def fib2(n):
result = []
a, b = 0, 1
while a < n:
result.append(a)
a, b = b, a + b
return result
if __name__ == "__main__":
import sys
# 命令行参数:python fibo.py 50
fib(int(sys.argv[1]))
直接运行 python fibo.py 50 会打印斐波那契数列;而 import fibo 时测试代码不会执行。这种模式也常用于单元测试:把测试套件写在 if __name__ == "__main__": 块里,既不影响导入,又能直接运行文件做自测。
模块间的交叉导入
模块可以导入其他模块。按照惯例,import 语句放在文件开头,但这并非强制要求。如果模块 A 导入了模块 B,那么 B 的名称会被加入 A 的全局命名空间:
# a.py
import b
print(b.B_VAR) # 访问 b 模块中的全局变量
# b.py
B_VAR = 100
import a # 如果 a.py 又 import b,不会无限循环,因为 b 正在初始化中
循环导入(A 导入 B,B 又导入 A)不会导致无限递归,因为 Python 的导入系统会在模块开始加载时就将其放入 sys.modules 缓存。但如果在循环中访问尚未完成初始化的属性,可能遇到 AttributeError。解决循环导入的最佳方案是重构代码,把共享定义移到第三个模块中。
导入失败的情况
当模块不存在或存在语法错误时,导入会抛出异常。ModuleNotFoundError 表示找不到模块;SyntaxError 或 ImportError 表示找到文件但无法正确加载:
>>> import nonexistent
ModuleNotFoundError: No module named 'nonexistent'
>>> from mathdemo import subtract
ImportError: cannot import name 'subtract' from 'mathdemo'
如果模块文件存在但名称拼写错误(如 MathDemo 与 mathdemo),Python 同样视为模块不存在,因为模块名区分大小写。
总结
import 语句的核心是命名空间管理:import mod 把模块对象引入当前命名空间,保持隔离;from mod import name 把具体名称注入当前命名空间,方便但需警惕覆盖;as 解决冲突和长名问题;importlib.reload 支持开发时的热重载;__name__ 让模块同时具备可导入性和可执行性。理解这些机制,才能写出既模块化又安全的 Python 代码。