当__getattr__()遇上@property,坑倒Python老司机

当__getattr__()遇上@property,坑倒Python老司机,第1张

概述今天同事反馈说我写的一个基础库有一个bug,大概就是自己写的类明明有属性foo,但会抛个类似下边的异常出来,AttributeError:'A'objecthasnoattribute'foo'这很让人困惑啊,因为抛出异常的函数是基类的__getattr__()方法,所以他就找我来解决了。我看代码也是一脸懵,这个foo就

今天同事反馈说我写的一个基础库有一个BUG,大概就是自己写的类明明有属性foo,但会抛个类似下边的异常出来,

AttributeError: 'A' object has no attribute 'foo'

这很让人困惑啊,因为抛出异常的函数是基类的__getattr__()方法,所以他就找我来解决了。

我看代码也是一脸懵,这个foo就摆在那里,这个BUG给了我一个眼见不为实的错觉,一时找不到方向。突然我发现这个foo上面顶着个@property的帽子(装饰器),咦,会不会和这个有关系呢?于是搜索一下,就找到了这篇文章Correct handling of AttributeError in __getattr__ when using property,上面的高赞回答完美解答了这个问题。下面我就以这个问答的代码为例再讲一下,这样大家可以只看我这篇了。

首先有这样的代码,这个代码纯作示例用的,没有任何逻辑意义,

class A:    @property    def F(self):        return self.moo # here should be an error    @property    def G(self):        return self.F    def __getattr__(self, name):        print('call of __getattr__ with name =', name)        if name == 'foo':            return 0        raise AttributeError("'{}' object has no attribute '{}'".format(type(self).__name__, name))a = A()print(a.G)

显然地,会以为抛出的异常是AttributeError: 'A' object has no attribute 'moo',但实际抛出的异常却是AttributeError: 'A' object has no attribute 'G'而且吐出来的堆栈是这样的:

Traceback (most recent call last):  line 18 in <module>    print(a.G)  line 15, in __getattr__    raise AttributeError("'{}' object has no attribute '{}'".format(type(self).__name__, name))AttributeError: 'A' object has no attribute 'G'

作为__getattr__()的作者,简直能气死个人。

消消气,那么为什么__getattr__()会把前面AtrributeError异常吃掉了呢?这就要先回顾一下__getatrr__()的调用时机,文档中说:

Called when the default attribute access fails with an AttributeError (either __getattribute__() raises an AttributeError because name is not an instance attribute or an attribute in the class tree for self; or __get__() of a name property raises AttributeError).

简单来说,当查找属性时,Python会先调用__getattribute__(),如果找不到(抛出AtrributeError异常),才会去尝试调用__getattr__()。还有一种情况就是如果property__get__()方法抛出AttributeError异常时,也会尝试调用__getattr__()

那么回到上例,使用@property修饰的成员函数就成一个描述器descriptor,当访问它的时候,实际调用的是描述器的__get__()方法,显然,print(a.G)先调用了a.G.__get__,然后它又调用了a.F.__get__,而这个方法引用了self.moo,因为moo属性不存在,就抛出了AtrributeError。根据上面文档的描述,此时会调用a__getattr__()方法,根据上面的代码,这个__getattr__()也会抛出AtrributeError,所以Python就把前一个异常吃掉了,只显示了后面那个牛头不对马嘴的异常。

如此说来,那就是要在__get__()抛出AtrributeError的时候接住异常,而不是由Python去处理。要实现这一点,在这个例子和我们项目的代码里,都是重载__getatrribute__()而不是__getattr__()。把上面的代码修改如下:

class A:    @property    def F(self):        return self.moo # here should be an error    @property    def G(self):        return self.F    def __getattribute__(self, name):        print('call of __getattribute__ with name =', name)        if name == 'foo':            return 0        else:            return super().__getattribute__(name)

再次执行的时候,就会发现抛出来的异常符合期望了:

Traceback (most recent call last):...AttributeError: 'A' object has no attribute 'moo'

而我们项目做了同样的修改之后,也解决了问题。这个事故告诉我们,轻易不要去重载__getattr__()

总结

以上是内存溢出为你收集整理的当__getattr__()遇上@property,坑倒Python老司机全部内容,希望文章能够帮你解决当__getattr__()遇上@property,坑倒Python老司机所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1187770.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-03
下一篇 2022-06-03

发表评论

登录后才能评论

评论列表(0条)

保存