Python描述符(descriptor)解密(4)

在元类中使用带标签的描述符

由于描述符的标签名和赋给它的变量名相同,所以有人使用元类来自动处理这个簿记(bookkeeping)任务。

class Descriptor(object):
def __init__(self):
#notice we aren't setting the label here
self.label = None

def __get__(self, instance, owner):
print '__get__. Label = %s' % self.label
return instance.__dict__.get(self.label, None)

def __set__(self, instance, value):
print '__set__'
instance.__dict__[self.label] = value

class DescriptorOwner(type):
def __new__(cls, name, bases, attrs):
# find all descriptors, auto-set their labels
for n, v in attrs.items():
if isinstance(v, Descriptor):
v.label = n
return super(DescriptorOwner, cls).__new__(cls, name, bases, attrs)

class Foo(object):
__metaclass__ = DescriptorOwner
x = Descriptor()

f = Foo()
f.x = 10
print f.x

__set__
__get__. Label = x
10

我不会去解释有关元类的细节——参考文献中David Beazley已经在他的文章中解释的很清楚了。 需要指出的是元类自动的为描述符添加标签,并且和赋给描述符的变量名字相匹配。

尽管这样解决了描述符的标签和变量名不一致的问题,但是却引入了复杂的元类。虽然我很怀疑,但是你可以自行判断这么做是否值得。

访问描述符的方法

描述符仅仅是类,也许你想要为它们增加一些方法。举个例子,描述符是一个用来回调property的很好的手段。比如我们想要一个类的某个部分的状态发生变化时就立刻通知我们。下面的大部分代码是用来做这个的:

class CallbackProperty(object):
"""A property that will alert observers when upon updates"""
def __init__(self, default=None):
self.data = WeakKeyDictionary()
self.default = default
self.callbacks = WeakKeyDictionary()

def __get__(self, instance, owner):
return self.data.get(instance, self.default)

def __set__(self, instance, value):
for callback in self.callbacks.get(instance, []):
# alert callback function of new value
callback(value)
self.data[instance] = value

def add_callback(self, instance, callback):
"""Add a new function to call everytime the descriptor updates"""
#but how do we get here?!?!
if instance not in self.callbacks:
self.callbacks[instance] = []
self.callbacks[instance].append(callback)

class BankAccount(object):
balance = CallbackProperty(0)

def low_balance_warning(value):
if value < 100:
print "You are poor"

ba = BankAccount()

# will not work -- try it
#ba.balance.add_callback(ba, low_balance_warning)

这是一个很有吸引力的模式——我们可以自定义回调函数用来响应一个类中的状态变化,而且完全无需修改这个类的代码。这样做可真是替人分忧解难呀。现在,我们所要做的就是调用ba.balance.add_callback(ba, low_balance_warning),以使得每次balance变化时low_balance_warning都会被调用。

但是我们是如何做到的呢?当我们试图访问它们时,描述符总是会调用__get__。就好像add_callback方法是无法触及的一样!其实关键在于利用了一种特殊的情况,即,当从类的层次访问时,__get__方法的第一个参数是None。

class CallbackProperty(object):
"""A property that will alert observers when upon updates"""
def __init__(self, default=None):
self.data = WeakKeyDictionary()
self.default = default
self.callbacks = WeakKeyDictionary()

def __get__(self, instance, owner):
if instance is None:
return self
return self.data.get(instance, self.default)

def __set__(self, instance, value):
for callback in self.callbacks.get(instance, []):
# alert callback function of new value
callback(value)
self.data[instance] = value

def add_callback(self, instance, callback):
"""Add a new function to call everytime the descriptor within instance updates"""
if instance not in self.callbacks:
self.callbacks[instance] = []
self.callbacks[instance].append(callback)

class BankAccount(object):
balance = CallbackProperty(0)

def low_balance_warning(value):
if value < 100:
print "You are now poor"

ba = BankAccount()
BankAccount.balance.add_callback(ba, low_balance_warning)

ba.balance = 5000
print "Balance is %s" % ba.balance
ba.balance = 99
print "Balance is %s" % ba.balance
Balance is 5000
You are now poor
Balance is 99

结语

希望你现在对描述符是什么和它们的适用场景有了一个认识。前进吧骚年!

参考文献

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/3dc6797b3f7b9e623fa19b1246a9fb50.html