2

すみません、言葉遣いの悪いタイトルです。簡単な例がそれを明確にすることを願っています。私がやりたいことをする最も簡単な方法は次のとおりです。

class Lemon(object):

    headers = ['ripeness', 'colour', 'juiciness', 'seeds?']

    def to_row(self):
        return [self.ripeness, self.colour, self.juiciness, self.seeds > 0]

def save_lemons(lemonset):
    f = open('lemons.csv', 'w')
    out = csv.writer(f)
    out.write(Lemon.headers)
    for lemon in lemonset:
        out.writerow(lemon.to_row())

これはこの小さな例では問題なく機能しますが、Lemon クラスでは「同じことを繰り返している」ように感じます。そして、私が書こうとしている実際のコード (エクスポートしている変数の数は 4 ではなく 50 以下であり、to_row は一連の奇妙な計算を行う多くのプライベート メソッドを呼び出します) では、扱いにくくなります。

行を生成するコードを作成するときは、リストを正しい順序で作成していることを確認するために、常に「headers」変数を参照する必要があります。出力される変数を変更したい場合は、to_row とヘッダーが並行して変更されていることを確認する必要があります (まさに、DRY が防止しようとしているようなものですよね?)。

このコードを設計するより良い方法はありますか? 関数デコレータで遊んでいますが、何も行き詰まっていません。理想的には、特定のレモン インスタンス (つまり、クラス変数またはクラス メソッドである必要があります) を持たずにヘッダーを取得できる必要があり、変数ごとに個別のメソッドを持ちたくありません。

4

2 に答える 2

2

この場合、getattr()あなたの友人です: 文字列名に基づいて変数を取得できます。例えば:

def to_row(self):
    return [getattr(self, head) for head in self.headers]

編集: header を適切に使用するには、オブジェクトseeds?の属性を設定する必要がありseeds?ます。setattr(self, 'seeds?', self.seeds > 0)returnステートメントのすぐ上。

于 2012-11-17T00:45:38.093 に答える
1

これを行うために、いくつかのメタクラスのシェナネガンを使用できます...

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]
于 2012-11-17T03:15:33.503 に答える