追踪Python导入
我的Python库刚刚把主要模块的名字从 foo.bar
改成了 foobar
。为了兼容以前的代码,foo.bar
还是可以用,但如果导入它会有一些警告。现在,似乎有些示例程序仍然在使用旧模块,但不是直接导入。
我想找到那个错误的 import
语句。有没有什么工具可以让我追踪导入的情况,找到问题所在,而不用翻遍所有的代码?
3 个回答
我会先介绍一个通用的解决方案,然后再说明如何将其调整为特定的使用案例。
通用解决方案
现在有更简单的方法来调试这些问题,利用了Python导入系统的新功能。基本上,只需将自己的模块查找器(MetaPathFinder)添加到sys.meta_path
中。下面是一个示例,展示一个脚本如何导入pandas
,并将所有成功导入的模块列在imported
中,而将未成功导入的模块列在could_not_be_imported
中:
import sys
imported = []
could_not_be_imported = []
class FirstFinder:
def find_spec(self, modulename, path=None, target=None):
imported.append(modulename)
class LastFinder:
def find_spec(self, modulename, path=None, target=None):
imported.remove(modulename)
could_not_be_imported.append(modulename)
# setup ("start recording imports")
sys.meta_path.insert(0, FirstFinder())
sys.meta_path.append(LastFinder())
import pandas # import anything here
# cleanup ("stop recording")
sys.meta_path = [
x for x in sys.meta_path if not isinstance(x, (FirstFinder, LastFinder))
]
成功导入的模块列表就是imported
:
>>> imported
['pandas',
'numpy',
'numpy._globals',
# ...
'pandas.io.sql',
'pandas.io.stata',
'pandas.io.xml',
'pandas.util._tester',
'pandas._version']
而could_not_be_imported
则是:
>>> could_not_be_imported
['pickle5',
'org',
'fcntl',
'backports_abc',
'six.moves',
'backports_abc',
'backports_abc',
# ...
'cloudpickle',
'numexpr',
'bottleneck',
'org',
'backports_abc',
'backports_abc',
'backports_abc']
注意:这两个列表中可能会有重复项,所有模块都是按导入顺序列出的。可以根据需要进行修改。
特定使用案例:查找错误的导入
将LastFinder
修改为:
class LastFinder:
def find_spec(self, modulename, path=None, target=None):
imported.remove(modulename)
print(
"\nMissing module! ",
f"Tried to import: {modulename}\n",
"Last few last imports:\n\t",
"\n\t ".join(imported[-10:]),
)
could_not_be_imported.append(modulename)
这样,每当有缺失的模块时,它会打印出最近成功导入的几个模块的列表,以及你尝试导入的模块。例如:
Missing module! Tried to import: bottleneck
Last few last imports:
pandas.core.ops.common
pandas.core.ops.docstrings
pandas.core.ops.mask_ops
pandas.core.ops.methods
pandas.core.missing
pandas.core.array_algos.quantile
pandas.core.sorting
pandas.core.arrays.boolean
pandas.core.arrays.masked
pandas.core.nanops
在这种情况下,这意味着pandas.core.nanops
是最后一个成功导入的模块,紧接着就是未成功导入的模块。因此,可以很容易地通过查看pandas.core.nanops.__file__
来检查出问题的导入在哪里。(在文件中查找"bottleneck"
)。在我的情况下,我发现:
# pandas\core\nanops.py
# ...
bn = import_optional_dependency("bottleneck", errors="warn")
这个是怎么工作的?
Python的导入系统会通过sys.meta_path
查找一个规范查找器,这个查找器会有一个find_spec
方法,返回的结果不是None
。如果返回None
,那么就会使用sys.meta_path
中的下一个查找器来进行导入。
编辑 foo.bar 模块,添加以下代码:
import pdb
pdb.set_trace()
当你导入 foo.bar 时,程序会在 pdb.set_trace() 这一行停下来,这时你可以进入调试模式,检查你的代码。比如,你可以使用 "w" 命令来打印出完整的调用栈。
用 -v
参数启动 Python 解释器:
$ python -v -m /usr/lib/python2.6/timeit.py
# installing zipimport hook
import zipimport # builtin
# installed zipimport hook
# /usr/lib/python2.6/site.pyc matches /usr/lib/python2.6/site.py
import site # precompiled from /usr/lib/python2.6/site.pyc
# /usr/lib/python2.6/os.pyc matches /usr/lib/python2.6/os.py
import os # precompiled from /usr/lib/python2.6/os.pyc
import errno # builtin
import posix # builtin
# /usr/lib/python2.6/posixpath.pyc matches /usr/lib/python2.6/posixpath.py
import posixpath # precompiled from /usr/lib/python2.6/posixpath.pyc
# /usr/lib/python2.6/stat.pyc matches /usr/lib/python2.6/stat.py
import stat # precompiled from /usr/lib/python2.6/stat.pyc
# /usr/lib/python2.6/genericpath.pyc matches /usr/lib/python2.6/genericpath.py
import genericpath # precompiled from /usr/lib/python2.6/genericpath.pyc
# /usr/lib/python2.6/warnings.pyc matches /usr/lib/python2.6/warnings.py
import warnings # precompiled from /usr/lib/python2.6/warnings.pyc
# /usr/lib/python2.6/linecache.pyc matches /usr/lib/python2.6/linecache.py
import linecache # precompiled from /usr/lib/python2.6/linecache.pyc
# /usr/lib/python2.6/types.pyc matches /usr/lib/python2.6/types.py
import types # precompiled from /usr/lib/python2.6/types.pyc
# /usr/lib/python2.6/UserDict.pyc matches /usr/lib/python2.6/UserDict.py
...
然后只需用 grep 查找你之前的模块。