上一次我们讲到Descriptor是作为类属性出现的,也就是说,对于f.a,这样的属性存取,其实是会被变换为
type(f).__dict__['a'].__get__(f, type(f))
也就是说,对于Descriptor a,他是被存储在类的字典 __dict__中的。而如果我们通过类对象来存取a,其实就变换为
F.__dict__['a'].__get__(None, F)
在实现Descriptor时需要注意的一点就是,如果该Descriptor对象名为foo,那么很显然,我们在对象实例中存取变量时不能再用foo,因为这会造成递归。所以以下代码是错误的:
class AClass(object): foo = Foo() class Foo(object): def __get__(self, instance, owner): return instance.foo
instance.foo会再次去调用Foo.__get__,从而导致递归,最简单的解决方法就是在实例变量前加上下划线_,变成_foo,这样就能解决这个问题。
因为Descriptor其实是实现__get__, __set__, __delete__中任意一个方法的对象,所以,如果一个Descriptor没有实现__get__,但是我们却以f.a来获取属性a的话,那么此时如果f.__dict__中有属性a,那么就返回这个属性,否则,descriptor对象本身将被返回。所以,如果我们要实现data descriptor,那么我们应该实现__set__, __get__,而不仅仅实现__set__(python定义实现__set__的是data descriptor)。
对于Non-data descriptor,也就是只实现了__get__方法的descriptor,那么如果实例中有属性a的话,将会override __get__的返回值。顾名思义,non-data descriptor应该不关心数据。
class MyProp(object):
def __get__(self, instance, owner):
return 1
class Foo(object):
bar = MyProp()
f = Foo()
print f.bar # return 1
f.__dict__['bar'] = 2
print f.bar # return 2
还有一点我们需要注意,就是如果我们使用property装饰器,而property实现的是data descriptor,所以如果我们只定义了getter,此时由于是data-descriptor,返回值还是会由getter提供。但是如果我们只提供了setter,没有提供getter,将会抛出AttributeError异常。
class Foo(object):
def bar(self):
return 1
bar = property(fset=bar)
f = Foo()
try:
f.bar
except AttributeError as e:
print str(e)
Reference:
[1] Python manual - Implementing Descriptors