我们可以使用一些元类恶作剧来做到这一点......
在 python 2 中,属性在字典中传递给元类,不保留顺序,我们还需要一个基类,以便我们可以区分应该映射到行中的类属性。在 python3 中,我们可以省去几乎所有这些基本描述符类。
import itertools
import functools
@functools.total_ordering
class DryDescriptor(object):
_order_gen = itertools.count()
def __init__(self, alias=None):
self.alias = alias
self.order = next(self._order_gen)
def __lt__(self, other):
return self.order < other.order
对于我们希望映射到行中的每个属性,我们都需要一个 Python 描述符。插槽是一种无需太多工作即可获取数据描述符的好方法。不过需要注意的是,我们必须手动删除帮助程序实例以使真正的插槽描述符可见。
class slot(DryDescriptor):
def annotate(self, attr, attrs):
del attrs[attr]
self.attr = attr
slots = attrs.setdefault('__slots__', []).append(attr)
def annotate_class(self, cls):
if self.alias is not None:
setattr(cls, self.alias, getattr(self.attr))
对于计算域,我们可以记忆结果。在没有内存泄漏的情况下,从带注释的实例中记忆是很棘手的,我们需要弱引用。或者,我们可以安排另一个插槽来存储缓存值。这也不是线程安全的,但非常接近。
import weakref
class memo(DryDescriptor):
_memo = None
def __call__(self, method):
self.getter = method
return self
def annotate(self, attr, attrs):
if self.alias is not None:
attrs[self.alias] = self
def annotate_class(self, cls): pass
def __get__(self, instance, owner):
if instance is None:
return self
if self._memo is None:
self._memo = weakref.WeakKeyDictionary()
try:
return self._memo[instance]
except KeyError:
return self._memo.setdefault(instance, self.getter(instance))
在元类上,找到我们上面创建的所有描述符,按创建顺序排序,并指示对新创建的类进行注释。这不能正确处理派生类,并且可以使用其他一些便利,例如__init__
所有插槽。
class DryMeta(type):
def __new__(mcls, name, bases, attrs):
descriptors = sorted((value, key)
for key, value
in attrs.iteritems()
if isinstance(value, DryDescriptor))
for descriptor, attr in descriptors:
descriptor.annotate(attr, attrs)
cls = type.__new__(mcls, name, bases, attrs)
for descriptor, attr in descriptors:
descriptor.annotate_class(cls)
cls._header_descriptors = [getattr(cls, attr) for descriptor, attr in descriptors]
return cls
最后,我们希望继承一个基类,以便我们可以拥有一个to_row
方法。这只是按顺序调用所有__get__
相应描述符的所有 s。
class DryBase(object):
__metaclass__ = DryMeta
def to_row(self):
cls = type(self)
return [desc.__get__(self, cls) for desc in cls._header_descriptors]
假设所有这些都隐藏起来,看不到,使用此功能的类的定义几乎没有重复。唯一的缺点是为了实用,每个字段都需要一个 python 友好的名称,因此我们有
alias
关联的'seeds?'
键has_seeds
class ADryRow(DryBase):
__slots__ = ['seeds']
ripeness = slot()
colour = slot()
juiciness = slot()
@memo(alias='seeds?')
def has_seeds(self):
print "Expensive!!!"
return self.seeds > 0
>>> my_row = ADryRow()
>>> my_row.ripeness = "tart"
>>> my_row.colour = "#8C2"
>>> my_row.juiciness = 0.3479
>>> my_row.seeds = 19
>>>
>>> print my_row.to_row()
Expensive!!!
['tart', '#8C2', 0.3479, True]
>>> print my_row.to_row()
['tart', '#8C2', 0.3479, True]