Python中提供了一些魔术方法来控制对象属性的访问,赋值,删除过程。
属性访问魔术方法
__getattr__(self, item)
__getattribute__(self, item)
其中__getattr__只有在属性不存在时会被调用,__getattribute__无论属性是否存在都会被调用,item参数就是要访问的属性。
属性赋值魔术方法
__setattr__(self, key, value)
给对象属性赋值或者添加新属性时会被调用。
属性删除魔术方法
__delattr__(self, item)
当删除一个对象属性时,该方法会被调用。
下面通过一个案例来展示上面三个魔术方法的用法,其中age属性的值通过birth_date元素来计算出来的。
class Person(object):
def __init__(self, name: str, birth_date: int):
self.name = name
self.birth_date = birth_date
self.age = 0
def __getattr__(self, item):
raise AttributeError(item + "属性不存在")
def __getattribute__(self, item):
print("getattribute: %s" %item)
return super(Person, self).__getattribute__(item)
def __setattr__(self, key, value):
print('__setattr__, key = %s, value = %s'%(key,value))
if key == 'birth_date':
super(Person, self).__setattr__('birth_date', value)
super(Person, self).__setattr__('age', 2020 - value)
else:
# 必须加上这一步 否则所有的属性添加都会失败
super(Person, self).__setattr__(key, value)
def __delattr__(self, item):
super(Person, self).__delattr__(item)
print('delattr: %s'%item)
p1 = Person('peter', 2010)
print(p1.name)
print(p1.age)
p1.gender = 'man'
del p1.age
# 保证异常打印顺序
time.sleep(1)
print(p1.age)
输出:
__setattr__, key = name, value = peter
__setattr__, key = birth_date, value = 2010
__setattr__, key = age, value = 0
getattribute: name
peter
getattribute: age
0
__setattr__, key = gender, value = man
delattr: age
getattribute: age
Traceback (most recent call last):
File "D:/pycharm workspace/oopdemo/magic_method/AttrDemo.py", line 89, in <module>
print(p1.age)
File "D:/pycharm workspace/oopdemo/magic_method/AttrDemo.py", line 61, in __getattr__
raise AttributeError(item + "属性不存在")
AttributeError: age属性不存在
案例中__setattr__方法控制添加属性和给属性赋值的过程,通过birth_date属性来计算出age属性的值。
在使用这些访问控制魔术方法需要注意一点,不能通过self.xxx(备注:这里指的是访问控制魔术方法)的方式来访问,这样可能会导致死循环。比如把案例中的__getattribute__方法改成下面这样:
def __getattribute__(self, item):
print("getattribute: %s" %item)
return self.__getattribute__(item)
执行后会抛出RecursionError异常。RecursionError: maximum recursion depth exceeded while calling a Python object。
原因是self.__getattribute__会调用自身,所以就出现了死循环。通过supr(Person, self)来调用_XXX_(备注:这里指访问控制魔术方法)可以避免递归调用。
也有人通过self.__dict__的方式来访问或修改属性,这种方式看上去可行,但是存在一个问题,因为self.__dict__本身也是对象的属性(只是这个属性比较特殊,它存放了对象的其它属性),所以每次访问self.__dict__都会触发__getattribute__和__getattr__方法,这完全没必要,如果在这两个方法里面存在日志,会输出大量没必要的日志。所以建议通过supr(Person, self)来调用。