pythongetattribute_Python的__getattribute__二三事

本来以为自己对__getattribute__已经比较了解了,结果用到的时候,才发现有一些知识点之前一直没有真正弄明白,记录如下(针对python3,python2差异较大):

object类有__getattribute__属性,因此所有的类默认就有__getattribute__属性(所有类都继承自object),object的__getattribute__起什么用呢?它做的就是查找自定义类的属性,如果属性存在则返回属性的值,如果不存在则抛出AttributeError。

还是看代码:

>>> class A:

def __getattribute__(self, name):

print('in A')

return super().__getattribute__(name)

>>> class B(A):

def spam(self):

print('spam')

>>> b = B()

>>> in A

b.spam()

in A

spam

当在命令行输入b.spam(,甚至括号还没有输入完整的时候,就已经触发了__getattribute__。可见,当实例在查找任何属性的时候(注意是实例,这是另一个知识点,后面再详谈),都会触发__getattribute__方法。

基于此特性,可以方便的使用类装饰器为其所有属性(不管是有的还是没有的)扩充功能,例如,加一个log记录功能,代码如下:

>>> def log_attr(cls):

cls_getattribute = cls.__getattribute__

def new_getattribute(self, name):

print('catch name')

return cls_getattribute(self, name)

cls.__getattribute__ = new_getattribute

return cls

>>> @log_attr

class C:

def __init__(self, x):

self.x = x

def spam(self):

print('spam')

>>> c = C(42)

>>> c.x

catch name

42

>>> catch name

c.spam()

catch name

spam

>>> c.y

catch name

Traceback (most recent call last):

File "", line 1, in

c.y

File "", line 5, in new_getattribute

return cls_getattribute(self, name)

AttributeError: 'C' object has no attribute 'y'

只有在实例的命名空间查找属性的时候,才会触发__getattribute__,在类的命名空间中查找,是不会触发__getattribute__的。

还是第一个例子:

>>> B.spam

>>> b = B()

>>> B.spam(b)

spam

可见,当类直接调用spam方法的时候,不会触发__getattribute__,而且当以B.spam(b)形式调用的时候,巧妙的绕过了__getattribute__。

接下来,是一个重要的知识点,即特殊的方法,如__len__,__str__等等操作符重载方法,当隐式调用的时候,在python3中会直接在类的命名空间中查找,因此是不会触发__getattribute__的!!如下:

>>> class C:

def __len__(self):

return 42

def __getattribute__(self, name):

print(f'catch {name}')

return super().__getattribute__(name)

>>> c = C()

>>> len(c)

42

but,当直接显示调用的时候,python不会将其当作啥特殊方法,仍然会从实例的命名空间查找,此时,就会触发__getattribute__:

>>> c.__len__()

catch __len__

42

最大的影响就是,在委托类型的代码中,操作符重载的方法代码得重新写,如下:

>>> class A:

def __len__(self):

return 42

def attr1(self):

print('attr1')

>>> class B:

def __init__(self):

self.a = A()

def __getattribute__(self, name):

if name == 'a':

return super().__getattribute__(name)

else:

return getattr(self.a, name)

>>> b.attr1()

attr1

>>> len(b)

Traceback (most recent call last):

File "", line 1, in

len(b)

TypeError: object of type 'B' has no len()

可见,attr1正确的被代理,但是len方法没有被代理,因为隐式调用的时候,直接在B类的命名空间查找__len__方法,根本就没有触发__getattribute__。如果显示调用,就可以被代理了。

>>> b.__len__()

42

最后,还有一个坑需要注意,如上例B类的__getattribute__方法中,判断一下a是否B自身的实例属性的代码不可少,是则调用父类的__getattribute__方法返回正确的self.a,否则在getattr(self.a, name)中,self.a会不断的触发__getattribute__,从而会陷入无限循环。

对了,最后还有一小点,有一个比较特殊的操作符重载方法,即dir,因为它会从实例的命名空间开始查找__dict__和__class__特殊方法,因此也会触发__getattribute__。

貌似关于__getattribute__的知识点就这些了,以上均为个人原创,平时学习的积累,打字不易,转载还请注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值