新风格的类引发的属性访问问题
2010 1 27 05:29 PM 1746次查看
最容易想到的就是手动创建一个属性,然后返回这个属性。然而这仍然会导致一次函数调用,这在Python里仍然会产生一点点的开销。
另一种方法就是直接将返回值设为该属性,避免后续访问仍然调用函数,这在传统类(classic class)中是可以实现的:
class Classic:
@property
def attr(self):
print 'in property'
self.attr = 'hello'
return 'hello'
o = Classic()
print o.attr
print o.attr
结果是:in property可以看到attr方法只被调用了一次,之后的访问就不再调用函数了。
hello
hello
然而将它换成新风格的类(new-style class),就会出现这个异常:AttributeError: can't set attribute。
原因很简单,因为传统类的实例在设置属性时是直接修改实例的__dict__属性(这可以看成是property的bug,从这层意义上来说,property的set不应该用于传统类),因此self.attr = 'hello'会被隐式转变为self.__dict__['attr'] = 'hello',这不会引起任何冲突。
而新风格的类在设置属性时,如果是被property封装过的,那么会调用property的第2个参数来设置。可我在设置property时只传递了1个attr参数,因此第2个参数为None,于是这个属性就变成只读的,而不能被重设。
为了重设这个值,我必须给property传递第2个参数,于是变成了这样:
class NewStyle(object):
def getAttr(self):
print 'in get'
self.attr = 'hello'
return 'hello'
def setAttr(self, value):
print 'in set'
self.attr = value
attr = property(getAttr, setAttr)
o = NewStyle()
print o.attr
print o.attr
可是运行时却又拿到一个错误:RuntimeError: maximum recursion depth exceeded。原因就是setAttr中的self.attr = value会被隐式转换成setAttr(self, value),也就是不断地递归调用自身了。
于是仿照传统类,直接写成self.__dict__['attr'] = value,结果为:
in get可以看到,访问仍然被当成函数调用了,根本没有访问__dict__。原因就是当新风格类的一个descriptor属性同时实现了__get__和__set__(或__delete__)后,就成为了一个data descriptor,它的优先级就高于__dict__了。
in set
hello
in get
in set
hello
而__set__这个方法被property改写了(即使为None),因此Python不会使用默认的策略去寻找__dict__,而是必须调用__get__。
一个简单的优化如下:
def getAttr(self):
print 'in get'
try:
return self.__dict__['attr']
except:
self.attr = 'hello'
return 'hello'
但这仍避免不了getAttr的调用。最终我也没找到好的办法,只能自己用descriptor来实现:
class Attr(object):
def __get__(self, obj, objtype):
print 'in get'
obj.attr = 'hello'
return 'hello'
class NewStyle(object):
attr = Attr()
o = NewStyle()
print o.attr
print o.attr
这个复杂的玩意终于实现了我想要的结果,性能也的确比一般的实现快6倍:in get原理就是只实现__get__,而不实现__set__和__delete__时,因此是一个non-data descriptor,优先级低于__dict__。
hello
hello
不过要注意的是,这种方法实际上设置的是类属性,在处理私有数据时显得有些危险。但Python本身就是很危险的语言,可以说约定的作用要大于编译器的限制。
最后,如果要设置多个属性的话,总会觉得不便,所以我从《How-To Guide for Descriptors》里抄了个property的实现:
class Property(object):
def __init__(self, fget):
self.fget = fget
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError, 'unreadable attribute'
return self.fget(obj)
class NewStyle(object):
@Property
def attr(self):
print 'in property'
self.attr = 'hello'
return 'hello'
o = NewStyle()
print o.attr
print o.attr
print NewStyle.attr.__get__(o)
0条评论 你不来一发么↓