跟踪Python导入模块

57

我的Python库刚刚将其主模块名从foo.bar更改为foobar。为了向后兼容,foo.bar仍然存在,但是导入它会引发一些警告。现在,似乎有些示例程序仍从旧模块导入,但不是直接导入。

我想找到错误的import语句。是否有任何工具可以允许我跟踪导入并找到罪魁祸首,而不必深入查看所有代码?

3个回答

84

使用-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命令查找您旧的模块。


35
如果你无法访问Python命令行(例如解释器被嵌入到其他应用程序中),那么你可以在环境变量中将PYTHONVERBOSE设置为1以达到相同的效果。 - AndyJost
1
这是一个非常有用的技巧。在shell中,导入通常可以正常工作,但在服务器设置中,路径可能会不同而出现问题。当你不在Python shell中时,使用PYTHONVERBOSE这个技巧可以拯救生命。 - brianz
6
顺便提一下,更高的 PYTHONVERBOSE 值会提供更多信息。如果设置为 1,只会告诉你选择了哪个路径,而更高的数字会显示尝试了哪些路径。在弄清楚为什么找不到某个包时非常有帮助。 - Evgen
2
这并没有提供导入的堆栈跟踪。是哪个文件调用了 import site?是 timeit.py 吗?还是 zipimport?在一个更加复杂的环境中,我需要知道这个跟踪,但是找不到它。 - cowlinator

8

编辑foo.bar模块,添加以下代码:

import pdb
pdb.set_trace()

当导入foo.bar时,在pdb模式下程序将停止于pdb.set_trace()处,您可以在这里调试代码。例如,您可以使用“w”命令打印完整的调用堆栈。

1

我将首先介绍一般解决方案,然后展示如何将其适应于这个特定的用例。

一般解决方案

现在有更简单的方法来调试这些问题,利用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中的下一个查找器进行导入。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接