写了5年多Python,才知道源文件不能随便起名

50 阅读3分钟

又到了决定午饭吃什么的时候了,单位周边的餐馆早就吃腻了,但也不能不吃。于是我打算写个小程序,让 Python 随机帮我选出午饭菜单。

我随手就新建了一个名为 random.py 的源文件(记住这个文件名!),然后不假思索地 import random。记得 random 包里有个能随机返回一个 [a, b] 之间整数的函数,好像叫 randint() 吧。

我又顺手另起一行写了一个 print(,AI 助手迅速提示 random.randint(0, 100))。按下 Tab 键自动补全代码,打开终端,输入 python3.11 random.py,一回车……

结果竟然报错了!

python3.11 random.py 竟然报错了

报错信息里提到“no attribute 'randint'”,可查了下文档,这个函数确实叫这个名字呢。还提到了“(most likely due to a circular import)”,这就一个单独的文件,怎么还有循环导入的问题呢?奇怪……

是不是 Python 版本的问题呢?我常用的是 Python 3.11,再拿 macOS 自带的 3.13 试试吧。

Python 3.13 提供了相当有价值的报错信息

虽然还是报错,但报错内容变了,提供了相当有价值的信息:

... consider renaming '/Users/user/Projects/tmp/random.py' since it has the same name as the standard library module named 'random' and prevents importing that standard library module

说是我写的 random.py 和标准库模块 random 重名了,导致后者没能正确导入,需要重命名我创建的 random.py

在 random.py 前面加了个前缀 my_,变成 python3 my_random.py,果然能正常运行了。(你可以试试,如果只是加一个 _ 作前缀,还是报错……(´・Å・`))。

遇到这个问题的程序员应该并不少,如 Stack Overflow 上这个热门帖子,

 Stack Overflow 上类似问题的帖子

发帖人是在名为 request.py 的文件中导入 request 这个包。

那为什么会出现这类问题呢?其实这与 Python 如何管理模块/包的搜索路径有关,也就是 Python 怎么知道去哪里找要导入的模块/包的。

当运行 python3 xxx.py 时,xxx.py 所在的目录,即当前目录会被添加到 sys.path 的开头,这意味着 Python 会先在当前目录中搜索模块/包对应的 xxx.py 文件,如果找不到,再从 sys.path 中的后续路径中继续查找。

回到一开始的 random.py。因为我把文件命名为 random.py正好和 Python 标准库里的 random 模块同名了。运行脚本时,Python 会优先在当前目录里查找同名模块,于是导入的并不是标准库里的 random,而是我自己写的 random.py,正如报错信息所述,这个文件里确实没有 randint() 函数。

说白了就是,当前目录的 xxx.py 能够覆盖、屏蔽内置模块或第三方模块中的同名 xxx.py 文件。术语叫作“shadowing modules”。

我们可以通过这段代码来确认这个机制,

当前目录会被添加到 sys.path 的开头

哎,在一个文件名上浪费了半天时间。

不过,这套添乱的机制可能也有派上用场的时候吧,比如单元测试。通过重写、覆盖 randint() 函数,就能把随机变成确定,在代码世界里造一尊真的不掷骰子的上帝

在代码世界里造一尊真的不掷骰子的上帝


总之,给 Python 的源文件起名时,应尽量避免和标准库或常见包(如 random.pyjson.pyrequests.py 等)重名。否则导入机制会优先找到你自己的文件,结果把标准库“屏蔽”掉,带来各种莫名其妙的错误。

🔚