これを行うために、いくつかのメタクラスのシェナネガンを使用できます...
Python 2では、属性は順序を維持せずにdictでメタクラスに渡されます。また、行にマップする必要があるクラス属性を区別できるように、基本クラスも使用する必要があります。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記述子が必要になります。スロットは、多くの作業を行わずにデータ記述子を取得するための優れた方法です。ただし、1つの注意点として、実際のスロット記述子を表示するには、ヘルパーインスタンスを手動で削除する必要があります。
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))
計算フィールドの場合、結果をメモ化できます。注釈付きインスタンスのメモ化は、メモリリークなしでは注意が必要です。weakrefが必要です。または、キャッシュされた値を保存するためだけに別のスロットを用意することもできます。これもスレッドセーフではありませんが、かなり近いです。
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__
これは、それぞれの記述子すべてに対して順番にすべてのを呼び出すだけです。
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]