2016-04-18 7 views
33

私は本質的には物事のコレクション/リストであるクラスを持っています。しかし、このリストにいくつかの追加機能を追加したいと思います。私が望むのは次のとおりです:クラスがPythonのリストのように動作するようにします

  • 私はインスタンスli = MyFancyList()です。変数liは、リストとして使用するたびにリストとして動作する必要があります。[e for e in li],、。
  • さらに、li.fancyPrint()li.getAMetric()li.getName()のような特殊機能が必要です。

私は現在、次のアプローチを使用します。

class MyFancyList: 
    def __iter__(self): 
    return self.li 
    def fancyFunc(self): 
    # do something fancy 

これは[e for e in li]などのイテレータとしての使用のためにOKですが、私はli.expand(...)のような完全なリストの振る舞いを持っていません。

最初に推測するのは、listMyFancyListに継承することです。しかし、これが推奨される非凡な方法ですか?はいの場合は、何を考慮する必要がありますか?いいえ、いい方法は何でしょうか?

+6

あなたは、2つのオプションを持っています'。時代のほとんどは2)1つは行く方法になります。 –

+4

@imaluengoまたは3)[抽象基本クラス](https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes)を使用してください – jonrsharpe

+4

4件のクローズリクエストがあります。何故ですか?時々私はスタックオーバーフローを理解していません...:-S – Michael

答えて

47

あなただけのリストの挙動の一部、使用組成物(すなわち、あなたのインスタンスが実際のリストへの参照を保持)とするために必要な唯一のメソッドを実装したい場合あなたが望む行動。これらのメソッドは、あなたのクラスの任意のインスタンスは、例えば、への参照を保持している実際のリストに作業を委任する必要がありますだけでは__getitem__を実装

def __getitem__(self, item): 
    return self.li[item] # delegate to li.__getitem__ 

は、例えば、反復やスライシングのために、あなたの機能の驚くべき量を与えるだろう。あなたがリストのフル行動をしたい場合は

>>> class WrappedList: 
...  def __init__(self, lst): 
...   self._lst = lst 
...  def __getitem__(self, item): 
...   return self._lst[item] 
... 
>>> w = WrappedList([1, 2, 3]) 
>>> for x in w: 
...  x 
... 
1 
2 
3 
>>> w[1:] 
[2, 3] 

は、collections.UserListから継承します。 UserListは、リストのデータ型の完全なPython実装です。

なぜlistから直接継承しないのですか?

list(またはC言語で書かれた他の組み込み関数)から直接継承する大きな問題の1つは、組み込み関数のコードが、ユーザーによって定義されたクラスでオーバーライドされた特別なメソッドを呼び出すかどうかということです。ここでpypy docsから関連の抜粋です:

を公式には、CPythonのは、まさに組み込み型暗黙的に呼び出さないか、取得のサブクラスのメソッドをオーバーライドしたときのために、まったくのルールを持っていません。近似として、これらのメソッドは、同じオブジェクトの他の組み込みメソッドによって呼び出されることはありません。例えば、dictのサブクラス内のオーバーライドされた__getitem__は、例えば、組み込みのgetメソッド。

ルチアーノRamalhoのFluent Pythonから別の引用、ページ351:

サブクラスは、辞書やリストなどの種類に内蔵または組み込みメソッドはほとんどUSER-無視しているため、直接がち エラー - であるSTR定義された オーバーライド。組み込み関数をサブクラス化する代わりに、簡単に拡張できるように設計されたコレクション モジュールのUserDict、UserListおよびUserStringからクラス を派生させます。

...ともっと、ページ370+:

ふらちな組み込み関数:バグや機能? 組み込みのdict、list、およびstrの型は、Python自体の不可欠なビルディングブロックです。したがって、 は高速でなければなりません。そのため、CPythonは、組み込みの メソッドがサブクラスによってオーバーライドされたメソッドと協調しないことによって誤動作を引き起こすショートカットを採用しました。

ビットの周りにプレーした後、list組み込みの問題はそれほど重要であると思われる(私はしばらくの間はPython 3.4でそれを破るしようとしましたが、本当に明白な予期しない動作を見つけられませんでした)が、私はまだ望んでいました原則的に何が起こるかのデモンストレーションを投稿するには、ので、ここでdictと1とUserDictです:

>>> class MyDict(dict): 
...  def __setitem__(self, key, value): 
...   super().__setitem__(key, [value]) 
... 
>>> d = MyDict(a=1) 
>>> d 
{'a': 1} 

>>> class MyUserDict(UserDict): 
...  def __setitem__(self, key, value): 
...   super().__setitem__(key, [value]) 
... 
>>> m = MyUserDict(a=1) 
>>> m 
{'a': [1]} 

あなたが見ることができるように、dictから__init__方法は、当社からの__init__方法ながら、上書き__setitem__方法を無視UserDictはそうしなかった。

+0

'collections.abc.MutableSequence'や' collections.abc.Sequence'ではなく 'collections.UserList'を使う理由を説明できますか?私は自分自身を知らない。 –

+1

@Gary: 'list'が' Sequence'と 'MutableSequence'インターフェースの一部でないものを望むなら、' UserList'を使います。例えば、 'collections.UserList'は' list'のような字句順を提供しますが、 'collections.abc.Sequence'と' MutableSequence'はそうではありません。なぜなら、比較はコレクションインターフェースの一部ではないからです。もう一つの例は '__add__'です(ただし' MutableSequence'には '__iadd__'があります)。 –

+0

'list'の例では、' + 'を許可する' __add__'の再定義など、 '+ ='はそのメソッドを使わないが、 '__iadd__'を暗黙のうちに再定義する必要があるということができます。またはさらに微妙な: '[1] + my_list'の要求' __radd__'は正しい結果を生成します。 – Bakuriu

5

ここで最も簡単な解決策はlistクラスから継承することです:あなたは、リストとしてMyFancyList型を使用し、その具体的な方法を使用することができます

class MyFancyList(list): 
    def fancyFunc(self): 
     # do something fancy 

継承により、オブジェクトとlistの間に強い結合が導入されます。実装するアプローチは、基本的にプロキシオブジェクトです。 使用方法は、オブジェクトを使用する方法によって大きく異なります。それがである場合リストである場合、継承はおそらく良い選択です。


EDIT:@acdrで指摘したように、リストのコピーを返すいくつかの方法が代わりにlistMyFancyListを返すためにオーバーライドする必要があります。

それを実装するための簡単な方法:

class MyFancyList(list): 
    def fancyFunc(self): 
     # do something fancy 
    def __add__(self, *args, **kwargs): 
     return MyFancyList(super().__add__(*args, **kwargs)) 
+6

リストのコピーを返すいくつかのメソッドをオーバーライドしたいかもしれないことを覚えておいてください。例えば。この場合、 'lst = MyFancyList()'、 'lst + [1,2,3]'はファンシーリストではなくプレーンリストを返します。これを動作させるには '__add__'メソッドをオーバーライドする必要があります。 – acdr

+3

もう1つの答えが指摘しているように、 'list'を直接継承することには落とし穴があります。だから結局のところ、なぜこれがdownvoteを発行したのかという疑問に対する良い答えではないと思います。 – Michael

+0

これはすべての単一の魔法を継承していれば問題ありません。個人的には、決してこれをすることはお勧めしません。 'Mapping'や' MutableMapping'や 'UserDict'を継承します。 –

3

はあなたのポスト(fancyPrintfindAMetric)に含まれる2つの例の方法に基づき、あなたがあなたのリストに余分な状態を格納する必要があるとは思えません。これが当てはまる場合は、これらを自由な関数として宣言し、サブタイプを完全に無視するのが最も簡単です。 listUserListのような問題を回避し、__add__の返り値のような壊れやすいエッジケース、予期しないLiskovの問題、&cなどの問題を完全に回避します。代わりに、関数を書いたり、出力のために単体テストを書いたり、すべてが意図どおりに動作することを保証することができます。

これは、あなたの関数が任意のの反復可能な型(ジェネレータ式など)で動作することを意味します。

+2

例の関数が非常に賢明に選択されていないことは事実です。実際には、リストクラスは実際にオブジェクトに関連付けられた状態を持ちます。名前などです。 – Michael

+1

これをクリアするマイケルの井戸:p私は単純にこれをコメントの質問として提出しましたが、残念ながら私はこのSEにコメントするのに十分な担当者がいません。 – gntskn

3

あなたがlistのすべての方法を再定義しない場合、私はあなたに次のアプローチを提案する:

class MyList: 
    def __init__(self, list_): 
    self.li = list_ 
    def __getattr__(self, method): 
    return getattr(self.li, method) 

これは、appendのようなメソッドを作るextendというように、箱から出して動作します。魔法の方法(例えば__len____getitem__など)あなたは、少なくともこのようにそれらを再宣言する必要がありますので、この場合には仕事に行くされていないこと、しかし、注意してください:

class MyList: 
    def __init__(self, list_): 
    self.li = list_ 
    def __getattr__(self, method): 
    return getattr(self.li, method) 
    def __len__(self): 
    return len(self.li) 
    def __getitem__(self, item): 
    return self.li[item] 
    def fancyPrint(self): 
    # do whatever you want... 

この場合、あなたならばということに注意してくださいlist(たとえばextend)のメソッドをオーバーライドしたい場合は、呼び出しが__getattr__メソッドを通過しないように独自のメソッドを宣言することができます。例えば:1)クラスにリストを追加し、すべての行動(または2を退屈である、外部 `list`の必要な部分)を、シミュレート)リスト`継承:

class MyList: 
    def __init__(self, list_): 
    self.li = list_ 
    def __getattr__(self, method): 
    return getattr(self.li, method) 
    def __len__(self): 
    return len(self.li) 
    def __getitem__(self, item): 
    return self.li[item] 
    def fancyPrint(self): 
    # do whatever you want... 
    def extend(self, list_): 
    # your own version of extend 
関連する問題