追踪Python导入

58 投票
3 回答
53633 浏览
提问于 2025-04-17 01:39

我的Python库刚刚把主要模块的名字从 foo.bar 改成了 foobar。为了兼容以前的代码,foo.bar 还是可以用,但如果导入它会有一些警告。现在,似乎有些示例程序仍然在使用旧模块,但不是直接导入。

我想找到那个错误的 import 语句。有没有什么工具可以让我追踪导入的情况,找到问题所在,而不用翻遍所有的代码?

3 个回答

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

>>> 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中的下一个查找器来进行导入。

8

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

import pdb
pdb.set_trace()

当你导入 foo.bar 时,程序会在 pdb.set_trace() 这一行停下来,这时你可以进入调试模式,检查你的代码。比如,你可以使用 "w" 命令来打印出完整的调用栈。

87

-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 查找你之前的模块。

撰写回答