一、 引言
在《第8.26节 重写Python类中的__getattribute__方法实现实例属性访问捕获》章节介绍了__getattribute__方法,可以通过重写该方法,截获所有通过“实例名.属性名”访问实例变量、类变量、实例方法的所有操作,这是一个非常符合Python风格的方法。类似的,Python提供了__setattr__方法截获所有给属性赋值的能操作,在本节的前2节介绍了调用__setattr__方法进行属性设置的方法,本节将介绍重写__setattr__方法并以此验证所有属性值的修改Python都会隐式地调用__setattr__方法。
二、 重写__setattr__方法
虽然可以动态重写__setattr__方法,但老猿并不建议这样做,我们还是建议在类中直接通过定义重写__setattr__()方法你,这样最高效也最符合编程者的思维。
- 语法:setattr(self, name, value)
- 语法释义:
1)self,就是对象自身,如果有不理解的,请参考老猿前面的博文《第7.5节 揭开Python类中self的面纱》;
2)name:需要定义的属性名字,为字符串类型。注意属性不仅是实例变量,也可能是实例方法,但不会是类变量,因为触发该方法只能通过“对象.属性”触发,而在使用该方式给类变量赋值时,并不是赋值给类变量,而是定义了一个与类变量同名的实例变量。具体请参考老猿前面的博文《第7.12节 可共享的Python类变量》;
3)value:属性需要设置的值; - 重写一个类的实例方法__setattr__后,在该类的任何个属性被尝试赋值时将调用重写的方法,这个调用会取代正常机制(即将值保存到实例字典)。因此在自定义类中重写__setattr__方法后,如果需要继续执行赋值给一个实例属性的任务,它应该调用同名的父类方法,例如 object.__setattr__方法, 或执行“实例.dict[属性名]=值”,如果调用自身的__setattr__方法执行赋值语句,会导致__setattr__方法的嵌套递归调用。
三、 案例
- 案例说明
1)定义类Car,类中除了构造方法,还有实例方法drive1和重写的__setattr__;
2)重写的__setattr__方法就是在调用时输出相关属性及其要设置的值,并为了保障属性值正常设置,调用了父类的__setattr__方法去设置属性的值;
3)在类体外定义了实例car;
4)在类体外定义一个函数fdrive,在定义实例后,将该函数动态赋值给类的实例变量drive2,并通过MethodType实现实例与该函数的绑定;
5)执行相关方法查看重写的__setattr__方法的执行情况以及对__dict__的影响。 - 案例代码
>>> from types import MethodType #导入MethodType方法
>>> def fdrive(self,distance):
print(f"In function fdrive:distance={distance}")
self.totaldistance += distance #定义一个函数,准备用于赋值给类的实例方法属性
>>> class Car():
def __init__(self, power):
self.power = power
self.totaldistance=0
def drive1(self,distance):
print(f"In method drive:distance={distance}")
self.totaldistance+=distance
def __setattr__(self,name,value):
print(f"execute __setattr__:name={name},value={value}")
self.__dict__[name]= value
>>> car = Car('汽油发动机')
execute __setattr__:name=power,value=汽油发动机
execute __setattr__:name=totaldistance,value=0
>>> car.drive1(100)
In method drive:distance=100
execute __setattr__:name=totaldistance,value=100
>>> car.__setattr__('drive2',MethodType(fdrive,car)) #用函数定义实例方法drive2
execute __setattr__:name=drive2,value=<bound method fdrive of <__main__.Car object at 0x00000000045E5A90>>
>>> car.drive2(100)
In function fdrive:distance=100
execute __setattr__:name=totaldistance,value=200
>>> car.__dict__ #查看自定义实例变量
{'power': '汽油发动机', 'totaldistance': 200, 'drive2': <bound method fdrive of <__main__.Car object at 0x00000000045E5A90>>}
>>>
- 执行截图
5. 案例总结
1)通过上述执行情况可以确认,无论是类体内的实例方法(含构造方法)执行还是类体外的实例变量赋值,都会触发__setattr__方法的执行。因此该自定义方法可以完全捕获所有对属性的设置,如果需要加控制逻辑可以在该自定义方法中增加;
2)动态绑定的实例方法,与类体内定义的实体方法不同,动态定义的在__dict__中有一个实例变量,而类体内预定义的则没有。这是因为动态定义本身就是一个赋值语句,肯定会触发实例变量的增加。
本节结合案例介绍了重写__setattr__方法的实现以及相关注意事项,通过重写类的实该方法__setattr__,可以捕获对该类的所有属性定义和赋值。