【问题标题】:How to debug a sys.modules entry that gets overwritten?如何调试被覆盖的 sys.modules 条目?
【发布时间】:2015-01-09 06:17:20
【问题描述】:

我正在尝试调试导致sys.modules['numpy'] 被覆盖的问题。我在numpy.__init__ 中添加了一些打印语句,当我尝试导入 numpy 时,我得到了以下输出:

numpy.__init__ running
id(sys.modules) = 89034704
id(sys.modules['numpy']) = 161528304
numpy.__init__ running
id(sys.modules) = 89034704
id(sys.modules['numpy']) = 177135864

Numpy 有许多循环导入,它们应该按照this answer 中的描述工作。但就我而言,不是从sys.modules 获取部分初始化的numpy 模块,而是再次导入numpy,然后再次执行numpy.__init__,导致崩溃。

我如何检测 sys.modules 以了解谁在覆盖 sys.modules['numpy'] 以及何时覆盖? 通常我会编写一个 dict 子类,但我认为更改 @ 并不安全987654331@ 指向我自己的对象。我尝试覆盖sys.modules.__setattr__,但这是一个只读属性。

上下文:我正在尝试在 Julia 库 PyCall 中调试 this issue。 PyCall 将 Python 解释器嵌入到正在运行的 Julia 进程中,并将导入委托给来自 cpython 的 PyImport_ImportModule。上面的问题发生在对PyImport_ImportModule 的一次调用中,所以我希望这个问题应该可以用python / cpython 的知识来回答,但不需要Julia / PyCall 的知识。

【问题讨论】:

  • 我会首先尝试仅在 Python 中重现该错误,而不涉及 Julia(或者,理想情况下,直接 C API 调用)。你有可以做到这一点的示例 Python 代码吗?
  • this question 的答案建议覆盖 __import__ 可能会起作用。我怀疑问题是由于某种路径混淆造成的。

标签: python numpy


【解决方案1】:

您可以将sys.modules 从普通的dict 更改为prints 分配,例如:

import sys
import traceback

class noisydict(dict):
    def __setitem__(self, key, value):
        print('ASSIGNED: key={!r} value={!r} at:'.format(key, value))
        traceback.print_stack()
        return dict.__setitem__(self, key, value)

sys.modules = noisydict(sys.modules)

如果覆盖发生在 C 代码中,这可能有效,也可能无效(此类代码可能直接访问底层的 dict.__setitem__,而不是像 Python 代码那样只执行 sys.modules[name] = newmodule),但值得一试!

【讨论】:

  • 不幸的是,这没有帮助;我尝试将您的代码放入文件sysmoduleshack.py。然后从解释器:import sysmoduleshack; import numpy 什么也不打印。我怀疑你是对的,字典是从 C 代码修改的。
  • @cberzan,那么你的生活会变得更艰难——而且 100% 依赖于你没有提到的 Python 的确切版本(导入系统经常变化)。至少尝试python -v 告诉您在您观察到的崩溃发生之前正在导入的确切内容 - 再次,这是一个开始。
  • 您忘记print 您创建的“ASSIGNED”字符串。但我认为修改sys.modulesimport 机制在C 级别这样做,所以我认为这不会起作用。事实上,当我尝试它时,它似乎完全阻止了导入的模块进入新的sys.modules
  • Tx @BrenBarn 发现了我的 thinko,现在进行了编辑以解决这个问题。
【解决方案2】:

感谢@BrenBarn 将我指向https://stackoverflow.com/a/14778568/744071。以下内容适用于我的目的:

importhack.py:

import traceback

old_import = __import__

def my_import(module, *args, **kwargs):
    print "my_import({}) caused by:".format(module)
    traceback.print_stack()
    return old_import(module, *args, **kwargs)

__builtins__['__import__'] = my_import

用法:

>>> import importhack
>>> import numpy

我认为 PyCall.jl 中的原始问题是由在 Python 解释器完全初始化之前调用 PyImport_ImportModule 引起的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-12-21
    • 1970-01-01
    • 1970-01-01
    • 2011-12-13
    • 2016-07-13
    • 2019-06-07
    • 1970-01-01
    相关资源
    最近更新 更多