一文搞懂__getattribute__
,__getattr__
,__setattr__
,__delattr__
__getattr__(self, name)
在属性被访问而对象没有这样的属性时自动调用,当属性可以通过正常机制追溯到时,__getattr__
是不会被调用的
class Sudent:
def __init__(self, name):
self.name = name
def __getattr__(self, item):
return '当{}属性不存在时调用我,存在就不用调用了'.format(item)
s = Sudent('zhangsan')
print(s.name) # zhangsan
print(s.age) # 当age属性不存在时调用我,存在就不用调用了
但是注意一定不能像下面这样调用
class Sudent:
def __init__(self, name):
self.name = name
def __getattr__(self, item):
return self.item
s = Sudent('zhangsan')
print(s.name) # zhangsan
print(s.age)
为什么呢?这样的话会出现无限递归错误
__getattribute__(self, name)
在属性被访问时自动调用(只适用于新式类),实例访问属性,__getattribute__
方法一直会被调用,无论属性是否能够追溯到。如果类还定义了__getattr__
方法,除非通过__getattribute__
显式的调用它,或者__getattribute__
方法出现AttributeError
错误,否则__getattr__
方法不会被调用了。
class Sudent:
def __init__(self, name):
self.name = name
def __getattribute__(self, item):
return '不管{}属性存不存在都要调用我'.format(item)
s = Sudent('zhangsan')
print(s.name) # 不管name属性存不存在都要调用我
print(s.age) # 不管age属性存不存在都要调用我
同样的如果在__getattribute__
方法下存在通过self.attr
访问属性,会出现无限递归错误。
__setattr__(self, name, value)
试图给属性赋值时自动调用,有则改无则增。
class Sudent:
def __init__(self, name):
self.name = name
def __setattr__(self, key, value):
print('属性{},属性值{}'.format(key, value))
s = Sudent('zhangsan') # 属性name,属性值zhangsan
s.age = 18 # 属性age,属性值18
print(s.name) # 报错
print(s.age) # 报错
同样的__setattr__
下还有self.attr
的赋值操作就会出现无限递归的调用__setattr__
的情况。并且自己实现__setattr__
有很大风险,一般情况都还是继承object
类的__setattr__
方法
class Sudent:
def __init__(self, name):
self.name = name
s = Sudent('zhangsan')
s.__setattr__('name', 'lisi')
print(s.name) # lisi
s.__setattr__('age', 18)
print(s.age) # 18
__delattr__(self, name)
试图删除属性时自动调用。
class Sudent:
def __init__(self, name):
self.name = name
def __delattr__(self, item):
print('删除的属性是{}'.format(item))
s = Sudent('zhangsan')
del s.name # 删除的属性是name
class Sudent:
def __init__(self, name):
self.name = name
s = Sudent('zhangsan')
print(s.name) # zhangsan
s.__delattr__('name')
print(s.name) # 报错
相比函数property,这些魔法方法使用起来要棘手些(从某种程度上说,效率也更低),但它们很有用,因为你可在这些方法中编写处理多个特性的代码
class Rectangle:
def __init__ (self):
self.width = 0
self.height = 0
def __setattr__(self, name, value):
if name == 'size':
self.width, self.height = value
else:
self. __dict__[name] = value
def __getattr__(self, name):
if name == 'size':
return self.width, self.height
else:
raise AttributeError()
s = Rectangle()
print(s.size) # (0, 0)
s.size = 10, 20
print(s.width) # 10
- 即便涉及的属性不是size,也将调用方法
__setattr__
。因此这个方法必须考虑如下两种情形:如果涉及的属性为size,就执行与以前一样的操作;否则就使用魔法属性__dict__
。__dict__
属性是一个字典,其中包含所有的实例属性。之所以使用它而不是执行常规属性赋值,是因为旨在避免再次调用__setattr__
,进而导致无限循环。 - 仅当没有找到指定的属性时,才会调用方法
__getattr__
。这意味着如果指定的名称不是size ,这个方法将引发AttributeError异常。这在要让类能够正确地支持hasattr和getattr等内置函数时很重要。如果指定的名称为size,就使用前一个实现中的表达式。 - 编写方法
__setattr__
时需要避开无限循环陷阱,编写__getattribute__
时亦如此