模块导入是 Python 里水最深的部分之一。循环导入、命名冲突、缓存机制……这 5 个坑,项目一大就容易碰到。
坑 1:importlib 缓存
这个坑我踩过,当时 debug 的情景还历历在目。
# 动态加载模块
import importlib
mod = importlib.import_module('config')
# 修改 config.py 文件
mod2 = importlib.import_module('config')
# mod2 和 mod 是同一个对象
你觉得输出是什么?想好了吗?
实际是:返回缓存的模块
Python 的模块系统有缓存机制(sys.modules),import_module 会先检查缓存。如果模块已经导入过,直接返回缓存的模块对象,不会重新执行模块代码。
正确写法:
import importlib
import sys
# 强制重新加载
if 'config' in sys.modules:
del sys.modules['config']
mod = importlib.import_module('config') # 现在会重新加载
踩坑现场:写了个配置热加载功能,发现修改配置文件后 import_module 拿到的还是旧配置。
坑 2:相对导入在脚本中不可用
把这个坑搞清楚之前,我在 logging 模块上纠结了一个下午。
报错:ImportError: attempted relative import with no known parent package
# mypackage/module.py
from .utils import helper
# 直接运行
# python mypackage/module.py → 报错
相对导入(from . import xxx)只能在包内使用,不能在直接运行的脚本中使用。直接运行 python module.py 时,Python 不知道这个文件属于哪个包,__package__ 为 None。
修复:
# 方案1:用 -m 方式运行
# python -m mypackage.module
# 方案2:用绝对导入
from mypackage.utils import helper
踩坑现场:写了个工具包,开发时直接运行单个模块测试,相对导入全部报错。
坑 3:循环导入
这个行为是 Python 的设计决定,但很多人到现在还以为是 bug。
报错:ImportError: cannot import name 'func_a' from partially initialized module 'a'
# a.py
from b import func_b
def func_a():
return 'a'
# b.py
from a import func_a
def func_b():
return 'b'
两个模块互相导入对方会导致循环导入错误。当 Python 导入 a.py 时,执行到 from b import func_b,开始导入 b.py,b.py 又要导入 a.py,此时 a.py 还没执行完,func_a 还不存在。
修复:把导入推迟到函数内部,或者重构依赖关系。
# a.py
def func_a():
return 'a'
def call_b():
from b import func_b # 延迟导入
return func_b()
踩坑现场:项目越写越大,模块之间依赖关系理不清,某天加了一行 import 整个服务起不来了。
坑 4:import 同名模块覆盖
你以为自己写对了,但它就是悄悄给你挖了个坑。
# 项目里有个文件叫 random.py
import random
print(random.randint(1, 10))
你觉得输出是什么?想好了吗?
实际是:AttributeError: module 'random' has no attribute 'randint'
如果你的项目里有个文件叫 random.py,Python 会优先导入你的文件而不是标准库的 random 模块。因为 Python 的模块搜索顺序是:当前目录 → 标准库 → 第三方库。
正确写法:
# 把文件改名,不要和标准库同名
# random.py → my_random.py
踩坑现场:新手教程里写了个 random.py 的练习文件,导入 random 模块怎么都不对,怀疑人生。
坑 5:reload 不更新已导入的名称
这段代码看起来完全正确,但它在生产环境上悄无声息地出了问题。
# 假设 config.py 中 DEBUG = True
from config import DEBUG
import importlib
import config
# 修改 config.py 中 DEBUG 为 False
importlib.reload(config)
print(DEBUG) # 你以为是 False
实际输出:True
from config import DEBUG 创建了一个新的本地变量 DEBUG,它绑定到导入时 config.DEBUG 的值。reload 更新了 config 模块对象,但你本地的 DEBUG 变量还是指向旧值。
正确写法:
# 方案1:用模块属性访问
import config
importlib.reload(config)
print(config.DEBUG) # 总是最新值
# 方案2:重新导入
from config import DEBUG
importlib.reload(config)
from config import DEBUG # 重新绑定
踩坑现场:在 Jupyter Notebook 里修改了配置模块,reload 后发现旧值还在,以为 reload 不好使。
说到底,踩坑是每个 Python 程序员的必修课。踩过了记下来,下次就绕开了。
这些坑你都知道吗?评论区交流一下你的翻车经历。
下期预告:编码问题专场
关于作者
写了 10 年 Python,赶上了机器学习的热潮,又撞上了大模型的浪头,每次以为学明白了,行业又变了一次。不是什么大神,就是一个在互联网里摸爬滚打、把坑踩了个遍的老工人。还在写代码,还在折腾,还在想明白这个时代到底发生了什么。把自己踩过的坑、走过的弯路、看过的热闹,说给还在路上的人听。
关注「鲁叶的Python」,一起穿越迷茫期。