为啥重新定义 __getattr__() 的对象会抛出 TypeError?

Posted

技术标签:

【中文标题】为啥重新定义 __getattr__() 的对象会抛出 TypeError?【英文标题】:Why does an object with redefined __getattr__() throws TypeError?为什么重新定义 __getattr__() 的对象会抛出 TypeError? 【发布时间】:2019-07-23 18:04:48 【问题描述】:

这里是代码

class MyTest: 
    def __init__(self):
         pass

    def __getattr__(self, attr):
        pass
t = MyTest()
print 'my test object: %r' %t

所以打印会触发TypeError: 'NoneType' object is not callable,而我只想查看对象是否存在。 授予此代码不是很有用。但是我在一个大代码库中有一个像这样的存根类,所以我做到了

if module and module.class and module.class.propery:
   # do something with that property
 ...

并得到了Type Error: 'NoneType' object is not callable,但该线路没有调用任何东西!我猜python是在幕后隐式调用一些函数。

奇怪的是,如果该类继承自 Object,则不会发生这种情况

发生了什么事?

【问题讨论】:

__getattr__ 应该返回一些东西,而不是 None @Jean-FrançoisFabre 歧视! None“某物”! 您的字符串格式化操作尝试访问t__repr__ 属性,并且由于您使用的是旧式类(您没有从object 继承),__getattr__正在返回 None 而不是实际返回要调用的所需方法。不要使用老式的类。 【参考方案1】:

在旧式类中,__getattr__ 用于更多种类的属性访问,包括魔术方法。 % 运算符试图调用 t.__repr__() 以填充 %r 占位符,但 t.__repr__t.__getattr__('__repr__') 评估,返回 None

if 的情况下,调用了不同的魔法方法,但出现了同样的问题。

>>> class Foo:
...   def __getattr__(self, attr):
...     print(attr)
...
>>> f = Foo():
>>> if f:
...   pass
__nonzero__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not callable

使用新型类,__getattr__ 仅在无法通过常规方法找到属性时调用(检查实例的 __dict__ 属性或实例的 MRO 中任何类的属性)。

>>> class Foo(object):
...     def __init__(self):
...         self.x = 3
...     def __getattr__(self, attr):
...         print(attr)
...
>>> f = Foo()
>>> if f:
...   pass
...
>>> f.x
3
>>> f.y
y

if f 的情况下,f 本身没有实现__nonzero____len__,其父级object 也没有实现,但在这种情况下,没有使用任何属性;事实上,f 是一个对象。在f.x中,x是在实例的属性dict中找到的,所以直接返回它的值。只有y(未由fFooobject 另行定义)调用对__getattr__ 的调用。

【讨论】:

so `if t` 也调用__repr__() ? 不,但是在布尔上下文中评估 t 会调用一些东西。使用 Jean-François Fabre 的建议,即在返回任何内容之前将参数打印到 __getattr__,应该表明调用 t.__nonzero__ 以确定 t 是否为真。【参考方案2】:

在带有老式类的 python 2 中,当您尝试在对象上调用 __repr__(打印时)时,您会调用 __getattr__

由于你暴力地存根这个方法,它返回None 并且python 尝试调用None(因为它期望返回一个方法)

尝试拨打object.__getattr__,这样就可以了:

class MyTest:
    def __init__(self):
         pass

    def __getattr__(self, attr):
        print(attr)   # so we see why getattr is called
        return object.__getattr__(self,attr)  # so it doesn't crash (neither it is useful...)

t = MyTest()
print ('my test object: %r' %t)

打印:

__repr__
my test object: <__main__.MyTest instance at 0x00000000031B3808>

这是一个特定的 python 2/旧样式对象问题。 Python 3 或新型对象的行为不同

【讨论】:

以上是关于为啥重新定义 __getattr__() 的对象会抛出 TypeError?的主要内容,如果未能解决你的问题,请参考以下文章

面向对象高级之 __getattr__,__setattr__,__delattr__

__setattr__和__delattr__和__getattr__

python cookbook第三版学习笔记十二:类和对象创建新的类或实例属性

python自定义属性访问__setattr__, __getattr__, __setattribute__

getattr(sys.modules[__name__], func_name)

面向对象__getattr__//__setattr__