如何在本地模块覆盖外部模块导入的标准库模块时导入外部模块?
我有一个本地模块叫做 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 个回答
解释器查找模块的路径保存在 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
问题不在于模块的名字,而在于你把一个模块当成脚本来运行。当 Python 运行一个脚本时,它会把这个脚本所在的文件夹放在sys.path
的第一位,这样从任何地方查找模块时,都会优先在这个文件夹里找。
为了避免这种情况,可以让 Python 以模块的方式来执行它:
python -m my_module.use_tokenize
当然,你也可以选择把可执行的脚本放在模块结构之外。