如何在本地模块覆盖外部模块导入的标准库模块时导入外部模块?

3 投票
2 回答
2524 浏览
提问于 2025-04-17 17:23

我有一个本地模块叫做 tokenize.py,它覆盖了标准库中同名的模块。我是在尝试导入一个外部模块(sklearn.linear_model)时发现这个问题的,因为这个外部模块会执行 import tokenize,它本来是想获取标准库的模块,但却得到了我的本地模块。

这个问题和 如何在Python中访问标准库模块,当有一个同名的本地模块时? 有关,但情况有所不同,因为按照上面的解决方案需要修改外部模块。

一个选择是重命名本地的 tokenize.py,但我不太想这样做,因为“tokenize”这个名字最能表达这个模块的功能。

为了说明这个问题,这里有一个模块结构的示意图:

   \my_module
      \__init__.py
      \tokenize.py
      \use_tokenize.py

在 use_tokenize.py 中,有以下的导入:

import sklearn.linear_model

当我运行 python my_module/use_tokenize.py 时,会出现以下错误:

Traceback (most recent call last):
  File "use_tokenize.py", line 1, in <module>
    import sklearn.linear_model
  <...>
  File "<EDITED>/lib/python2.7/site-packages/sklearn/externals/joblib/format_stack.py", line 35, in <module>
    generate_tokens = tokenize.tokenize
AttributeError: 'module' object has no attribute 'tokenize'

有没有什么方法可以在导入外部模块时忽略本地模块呢?

补充:由于评论提到解决方案因Python版本而异,所以我添加了python2.7作为标签。

2 个回答

1

解释器查找模块的路径保存在 sys.path 里面。为了防止第三方模块在导入时看到本地模块,我们需要把路径中的 . 去掉。这样做可以通过以下方式实现:

import sys
sys.path = sys.path[1:]
import sklearn.linear_model #using the original example.

不过,如果本地的 tokenize 已经被导入了,这个方法就不管用了。而且即使我们把之前的 sys.path 恢复,也会阻止本地的 tokenize 被导入:

import sys
old_path = sys.path
sys.path = sys.path[1:]
import sklearn.linear_model #using the original example.
sys.path = old_path

这是因为 Python 解释器会维护一个已导入模块的内部映射,这样后续对同一个模块的请求就会从这个映射中获取。这个映射对解释器来说是全局的,所以无论在哪里运行 import tokenize,都会返回同一个模块——这正是我们想要改变的行为。为了实现这个目标,我们需要修改这个映射。最简单的方法就是直接从 sys.modules 中删除相关的条目。

import sys
old_path = sys.path
sys.path = sys.path[1:]
import sklearn.linear_model #using the original example.
sys.path = old_path
del sys.modules['tokenize'] #get of the mapping to the standard library tokenize
import tokenize #cause our local tokenize to be imported    
4

问题不在于模块的名字,而在于你把一个模块当成脚本来运行。当 Python 运行一个脚本时,它会把这个脚本所在的文件夹放在sys.path的第一位,这样从任何地方查找模块时,都会优先在这个文件夹里找。

为了避免这种情况,可以让 Python 以模块的方式来执行它:

python -m my_module.use_tokenize

当然,你也可以选择把可执行的脚本放在模块结构之外。

撰写回答