__setattr__是python重载协议中的内容,用于在实例中设置属性。
实际使用方式:
class Old(object):
def __init__(self):
pass
def __setattr__(self, key, value):
print "__setattr__: ", key, value
object.__setattr__(self, key, value)
def __getattribute__(self, key):
print "__getattribute__: ", key
return object.__getattribute__(self, key)
def __getattr__(self, key):
print "run __getattr__ in undefinied param: ", key
return object.__getattribute__(self, key)
o = Old()
o.ok = 123
print o.ok
print o.undef
运行结果:
__setattr__: ok 123
__getattribute__: ok
123
__getattribute__: undef
run __getattr__ in undefinied param: undef
Traceback (most recent call last):
File "dict_object.py", line 36, in
print o.undef
File "dict_object.py", line 31, in __getattr__
return object.__getattribute__(self, key)
AttributeError: 'Old' object has no attribute 'undef'
o.undef 是一个为定义的实例属性,会被__getattr__捕捉到,但是在实例上没有这个属性,最后还是会报错。
但是如果我们现在的需求是这样,预先在实例的__init__函数上上设置一个变量,例如:self.data = {}
我们让接下来的__setattr__和__getattribute__或__getattr__的存取属性都在self.data的字典中进行,如果还是使用上面的代码,将会带来极大的麻烦。
因为我们首先要取得实例的data属性,即self.data,但是这个动作又必须经过__getattr__或__getattribute__操作,可能会这样写:
class Old(object):
def __init__(self):
self.data = {}
def __setattr__(self, key, value):
print "__setattr__: ", key, value
data = object.__getattribute__(self, 'data')
data[key] = value
def __getattribute__(self, key):
print "__getattribute__: ", key
return object.__getattribute__(self, key)
def __getattr__(self, key):
print "run __getattr__ in undefinied param: ", key
return object.__getattribute__(self, key)
o = Old()
o.ok = 123
print dir(o)
运行结果:
__setattr__: data {}
Traceback (most recent call last):
File "dict_object.py", line 35, in
o = Old()
File "dict_object.py", line 19, in __init__
self.data = {}
File "dict_object.py", line 23, in __setattr__
data = object.__getattribute__(self, 'data')
AttributeError: 'Old' object has no attribute 'data'
我们甚至连self.data都没有赋值成功! 为什么呢? 还是因为这一句:
data = object.__getattribute__(self, 'data')
因为__init__里面赋值data给self要经过__getattribute__的拦截,但是此时实例上还没有data属性,上面一行代码当然会报属性错误!
OK,我们再加上异常捕获怎么样? 尝试忽略这个异常,让我们试试:(不过在尝试之前,大家可能想到结果了)
class Old(object):
def __init__(self):
self.data = {}
def __setattr__(self, key, value):
print "__setattr__: ", key, value
try:
data = object.__getattribute__(self, 'data')
data[key] = value
except:
pass
def __getattribute__(self, key):
print "__getattribute__: ", key
return object.__getattribute__(self, key)
def __getattr__(self, key):
print "run __getattr__ in undefinied param: ", key
return object.__getattribute__(self, key)
o = Old()
o.ok = 123
print dir(o)
运行结果:
__setattr__: data {}
__setattr__: ok 123
__getattribute__: __dict__
__getattribute__: __members__
run __getattr__ in undefinied param: __members__
__getattribute__: __methods__
run __getattr__ in undefinied param: __methods__
__getattribute__: __class__['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattr__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
OK,运行成功,代码没有报错。中间淡色的部分可以忽略,它是dir函数获取实例上属性的过程。 注意加粗的部分,实例上根本没有data这个属性!
所以这个方法行不通。
那咋办捏?
有一个办法,在《Python学习手册》第4版中,提到了用实例的__dict__命名空间来预先分配好实例的属性:
class Old(object):
def __init__(self):
self.__dict__['data'] = {}
def __setattr__(self, key, value):
print "__setattr__: ", key, value
#data = object.__getattribute__(self, 'data')
#data[key] = value
# 或者直接这样:
self.data[key] = value
def __getattribute__(self, key):
print "__getattribute__: ", key
return object.__getattribute__(self, key)
def __getattr__(self, key):
print "run __getattr__ in undefinied param: ", key
return object.__getattribute__(self, key)
o = Old()
o.ok = 123
print o.data
OK,这样没问题了。
如果我们既想简单模拟类似dict类的工作方式,又想实现以属性的方式访问,我们可以直接重载__getitem__和__setitem__和__delitem__方法,并使用上面的代码:
class Old(object):
def __init__(self):
self.__dict__['data'] = {}
def __setattr__(self, key, value):
self.data[key] = value
def __getattr__(self, key):
# 这里我们用了一个取巧的方法
# 因为self.__dict__还是会被__getattribute__拦截到,导致无限递归
# 所以不能用它来实现
return self.data[key]
def __setitem__(self, key, value):
self.data[key] = value
def __getitem__(self, key):
try:
return self.data[key]
except IndexError:
pass
def __delitem__(self, key):
del self.data[key]
o = Old()
o.first = 123
print o.first
o['second'] = 456
print o['second']
特别说明:
在上面的代码中,属性访问使用了__getattr__,虽然只有在实例未定义该属性时才会被__getattr__拦截。
但是因为我们的__setattr__已经把属性保存在了self.data里面,所以每次__getattr__都会被调用,算是一种投机取巧的方式。
最终的原因还是因为,self.__dict__也会被__getattribute__拦截,所以不能在__getattribute__中访问self.__dict。