3

少しの例が私の質問を明確にするのに役立ちます:

セキュリティオブジェクトのリストとして動作させたいセキュリティとユニバースの2つのクラスを定義します。

これが私のサンプルコードです:

class Security(object):
    def __init__(self, name):
        self.name = name

class Universe(object):
    def __init__(self, securities):
        self.securities = securities

s1 = Security('name1')
s2 = Security('name2')
u = Universe([s1, s2])

ユニバースクラスで、enumerate()、len()、__ getitem __()などの通常のリスト機能を使用できるようにしたいと思います...:

enumerate(u)
len(u)
u[0]

だから私は自分のクラスを次のように定義しました:

class Universe(list, object):
    def __init__(self, securities):
        super(Universe, self).__init__(iter(securities))
        self.securities = securities

それはうまくいくようですが、それを行うのに適切なpythonicの方法ですか?

[編集]

リストをサブセット化すると、上記の解決策が期待どおりに機能しません。

>>> s1 = Security('name1')
>>> s2 = Security('name2')
>>> s3 = Security('name3')
>>> u = Universe([s1, s2, s3])
>>> sub_u = u[0:2]
>>> type(u)
<class '__main__.Universe'>
>>> type(sub_u)
<type 'list'>

変数sub_uをUniverseタイプのままにしておきたいです。

4

2 に答える 2

6

listこれらの機能を使用するために、実際にである必要はありません。それがダックタイピングの要点です。を定義するものはすべて、、、、、およびその他のさまざまなものを自動的__getitem__(self, i)に処理しx[i]ます。また、定義し、、なども機能します。または、の代わりに定義することもできます。または両方。それはあなたがどれだけ-yになりたいかによります。for i in xiter(x)enumerate(x)__len__(self)len(x)list(x)__iter____getitem__list

Pythonの特別なメソッドに関するドキュメントでは、それぞれの目的を説明し、それらを非常にうまく整理しています。

例えば:

class FakeList(object):
    def __getitem__(self, i):
        return -i
fl = FakeList()
print(fl[20])
for i, e in enumerate(fl):
    print(i)
    if e < -2: break

list見えない。

実際にrealリストがあり、そのデータを自分のものとして表現したい場合は、委任と継承の2つの方法があります。どちらも機能し、さまざまなケースでどちらも適切です。

オブジェクトが本当にプラスである場合は、継承を使用してくださいlist基本クラスの動作を踏んでいることに気付いた場合は、とにかく委任に切り替えたいと思うかもしれませんが、少なくとも継承から始めてください。これは簡単:

class Universe(list): # don't add object also, just list
    def __init__(self, securities):
        super(Universe, self).__init__(iter(securities))
        # don't also store `securities`--you already have `self`!

をオーバーライドすることもできます。これにより、初期化時間ではなく作成時にに入ることができますが、これは通常、では問題になりませ__new__ん。(のような不変型の場合はより重要です。)iter(securities)listliststr

オブジェクトがリストではなくリストを所有しているという事実がその設計に固有である場合は、委任を使用してください

委任する最も簡単な方法は明示的にです。を偽造するために定義するのとまったく同じメソッドを定義し、listそれらすべてを自分の所有するものに転送しlistます。

class Universe(object):
    def __init__(self, securities):
        self.securities = list(securities)
    def __getitem__(self, index):
        return self.securities[index] # or .__getitem__[index] if you prefer
    # ... etc.

また、次の方法で委任を行うこともできます__getattr__

class Universe(object):
    def __init__(self, securities):
        self.securities = list(securities)
    # no __getitem__, __len__, etc.
    def __getattr__(self, name):
        if name in ('__getitem__', '__len__',
                    # and so on
                   ):
            return getattr(self.securities, name)
        raise AttributeError("'{}' object has no attribute '{}'"
                             .format(self.__class__.__name__), name)

のメソッドの多くはlist新しいを返すことに注意してくださいlist。代わりに新しいものを返すようにしたい場合はUniverse、それらのメソッドをラップする必要があります。ただし、これらのメソッドの一部は二項演算子であることに注意してください。たとえば、が1つの場合のみ、両方がそうである場合、またはどちらかがそうである場合にのみa + b返す必要がありますか?Universea

また、 aまたは単一のオブジェクト__getitem__を返すことができ、前者を。でラップするだけなので、少し注意が必要です。これを行うには、の戻り値を確認するか、 ;のインデックスを確認します。どちらが適切かは、sをaの要素として使用できるかどうか、および抽出時にaとして処理するかaとして処理するかによって異なります。さらに、継承を使用している場合、Python 2では、非推奨のフレンドをラップする必要があります。これは、それらをサポートしているためです(ただし、要素ではなく常にサブリストを返すため、非常に簡単です)。listUniverseisinstance(ret, list)isinstance(index, slice)listUniverselistUniverse__getslice__list__getslice__

これらのことを決定したら、少し面倒な場合でも、実装は簡単です。これは、トリッキーなために使用した3つのバージョンすべての例__getitem__と、コメントで質問したものです。ラッピングに汎用ヘルパーを使用する方法を示しますが、この場合は1つのメソッドにしか必要ないため、やり過ぎかもしれません。

継承:

class Universe(list): # don't add object also, just list
    @classmethod
    def _wrap_if_needed(cls, value):
        if isinstance(value, list):
            return cls(value)
        else:
            return value
    def __getitem__(self, index):
        ret = super(Universe, self).__getitem__(index)
        return _wrap_if_needed(ret)

明示的な委任:

class Universe(object):
    # same _wrap_if_needed
    def __getitem__(self, index):
        ret = self.securities.__getitem__(index)
        return self._wrap_if_needed(ret)

動的な委任:

class Universe(object):
    # same _wrap_if_needed
    @classmethod
    def _wrap_func(cls, func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            return cls._wrap_if_needed(func(*args, **kwargs))
    def __getattr__(self, name):
        if name in ('__getitem__'):
            return self._wrap_func(getattr(self.securities, name))
        elif name in ('__len__',
                      # and so on
                      ):
            return getattr(self.securities, name)
        raise AttributeError("'{}' object has no attribute '{}'"
                             .format(self.__class__.__name__), name)        

私が言ったように、これはこの場合、特に__getattr__バージョンではやり過ぎかもしれません。のような1つのメソッドをオーバーライドし、他のすべてを委任したい場合は__getitem__、いつでも__getitem__明示的に定義し、他の__getattr__すべてを処理させることができます。

この種のラッピングを頻繁に行う場合は、ラッパークラスを生成する関数、またはスケルトンラッパーを記述して詳細を入力できるクラスデコレータなどを作成できます。詳細はユースケースによって異なるため(すべて上で述べた問題は、いずれかの方向に進む可能性があります)、必要なことを魔法のように実行する万能のライブラリはありませんが、ActiveStateには、より完全な詳細を示すレシピがいくつかあります。標準ライブラリソースのいくつかのラッパーですらあります。

于 2013-01-10T00:03:20.350 に答える
5

listとの両方から継承する必要はありませんが、これは妥当な方法ですobjectlistだけで十分です。また、クラスがリストの場合、格納する必要はありませんself.securities。リストの内容として保存されます。

ただし、クラスを何に使用するかによっては、リストを内部に格納するクラスを ( を格納していたようにself.securities) 定義してから、(場合によっては) メソッドに渡すメソッドをクラスに定義する方が簡単な場合があります。から継承する代わりに、この格納されたリストのlist。Python の組み込み型は、どのメソッドが他のどのメソッドに依存するか (たとえば、かどうかappendが に依存するかどうかinsert)に関して厳密なインターフェイスを定義していません。 -クラス。

編集:あなたが発見したように、新しいリストを返す操作はすべてこのカテゴリに分類されます。メソッドをオーバーライドせずにサブクラス化listし、オブジェクトのメソッドを (明示的または暗黙的に) 呼び出すと、基になるlistメソッドが呼び出されます。これらのメソッドは、プレーンな Python リストを返すようにハードコードされており、オブジェクトの実際のクラスが何であるかをチェックしないため、プレーンな Python リストを返します。

于 2013-01-09T23:09:41.383 に答える