可能会中断模块名称空间分配吗?

时间:2018-06-20 23:03:12

标签: python

我正试图找出错误的原因:https://github.com/numba/numba/issues/3027

似乎对于某些较旧的ubuntu安装,编译递归numba函数会使sys.stdout重置。我已验证sys is not being reloaded,而必须是某处正在分配给sys。因此,如果要分配sys名称空间的成员,我想安排一个断点。这可能吗?

重新分配:sys.__setattr____builtins__.setattr似乎不成功:

import sys

def f(*args, **kw):
    print >>sys.stderr, args, kw
    raise Exception("Break here")  # Never reached

print >>sys.stderr, sys.__setattr__
sys.__setattr__ = f
__builtins__.setattr = f
print >>sys.stderr, sys.__setattr__ #Changed


sys.stdout = 12345 # Should cause exception.

此外,sys名称空间还有一些不可思议的地方:

pprint.pprint(dir(sys)) # No __setattr__ here
print "__setattr__" in dir(sys) # False. Nope

# But this:
print >>sys.stderr, sys.__setattr__  # <method-wrapper '__setattr__' of module object at 0x7f8065b6cbb0>

2 个答案:

答案 0 :(得分:2)

__setattr__special method

  

对于自定义类,只有在对对象的类型(而不是在对象的实例字典中)进行定义的情况下,才能保证对特殊方法的隐式调用可以正常工作。

Python并未在任何地方准确记录哪些特殊方法会跳过实例字典,实际上,这确实取决于每个实现,但是(至少在CPython和PyPy中)__setattr__直接进入类型。

请注意,PEP 562在Python 3.7上为__getattr__上的__dir__ModuleType添加了一种特殊情况,但不包括__setattr__

因此,分配sys.__setattr__无效。

这也是'__setattr__' in dir(sys)为False的原因-就像对于任何非类型的东西一样。 dir函数不会返回在类中找到的属性(或从基类继承的属性)。如果要检查属性,通常使用hasattr(sys, '__setattr__')-或更好的方法是尝试访问该属性(因为即使使用自定义__getattribute__创建的动态属性,该属性也可以使用)。 / p>

此外,这意味着设置断点的位置将为types.ModuleType.__setattr__(或type(sys).__setattr__,即同一位置)。但这在CPython中不起作用,因为这是内置类型(实际上只是从object.__setattr__继承的)上的C函数插槽,而不是Python方法。


有两种传统的解决方法。不能保证它们都与内置模块一起使用。通过快速测试(使用CPython 3.7),第一个有效,但第二个无效。但是请在您自己的Python实现/版本上尝试它们。


首先,您可以创建一个子类:

class HookedModuleType(types.ModuleType):
    def __setattr__(self, name, value):
        print(f'{self.__name__}.__setattr__({name}, {value})')
        return super().__setattr__(name, value)

…,然后重新键入模块:

mymodule.__class__ = HookedModuleType

或者,由于ModuleType不会覆盖__setattr__的默认行为,因此它只是从object继承而来,这意味着它所做的全部都是self.__dict__[name] = value设置的。因此,您可以编写一个dict来拦截__setitem__并获得相同的效果:

class HookedDict(dict):
    def __setitem__(self, key, value):
        print(f'{self._name}.__setitem__({key}, {value})')
        return super().__setitem__(key, value)

mymodule.__dict__ = HookedDict(mymodule.__dict__)
mymodule.__dict__._name = mymodule.__name__

如果这些都不起作用,则必须创建一个稍微复杂一些的类来代理实际的模块对象:

class ModuleProxy(object):
    def __init__(self, module):
        object.__setattr__(self, '_module', module)
    def __getattr__(self, name):
        return getattr(self._module, name)
    def __delattr__(self, name):
        delattr(self._module, name)
    def __setattr__(self, name, value):
        print(f'{self._module.__name__}.__setattr__({name}, {value})')
        setattr(self._module, name, value)

...,然后用该模块的代理替换该模块:

sys = sys.modules['sys'] = ModuleProxy(sys)

这个错误更容易出错,在某些情况下可能会导致一些奇怪的行为,但是它并不依赖于任何非保证的行为,并且似乎可以在CPython 3.7、3.6和2.7和PyPy 5.10 / 3.5和5.10 / 2.7(显然2.7需要将f字符串更改为format调用)。

答案 1 :(得分:2)

代理或包装器类将起作用:

class DebugModule(object):
    def __init__(self, module):
       self.__dict__["_module"] = module
    def __getattr__(self, name):
        return getattr(self._module, name)
    def __setattr__(self, name, value):
        setattr(self._module, name, value)
        print("{}.{} = {}".format(self._module.__name__, name, repr(value)))

用法:

import sys
sys = DebugModule(sys)