我的Python库刚刚将其主模块名从foo.bar
更改为foobar
。为了向后兼容,foo.bar
仍然存在,但是导入它会引发一些警告。现在,似乎有些示例程序仍从旧模块导入,但不是直接导入。
我想找到错误的import
语句。是否有任何工具可以允许我跟踪导入并找到罪魁祸首,而不必深入查看所有代码?
使用-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命令查找您旧的模块。
编辑foo.bar模块,添加以下代码:
import pdb
pdb.set_trace()
我将首先介绍一般解决方案,然后展示如何将其适应于这个特定的用例。
现在有更简单的方法来调试这些问题,利用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
['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
中的下一个查找器进行导入。
PYTHONVERBOSE
值会提供更多信息。如果设置为 1,只会告诉你选择了哪个路径,而更高的数字会显示尝试了哪些路径。在弄清楚为什么找不到某个包时非常有帮助。 - Evgenimport site
?是timeit.py
吗?还是zipimport
?在一个更加复杂的环境中,我需要知道这个跟踪,但是找不到它。 - cowlinator