2015-01-12 7 views
18

__getitem__をきれいに書くには?シーケンスの種類を実装するとき、私はしばしば自分自身がこのようなコードを書く見つける(相対的に言って)のPythonでは、

class FooSequence(collections.abc.Sequence): 
    # Snip other methods 

    def __getitem__(self, key): 
     if isinstance(key, int): 
      # Get a single item 
     elif isinstance(key, slice): 
      # Get a whole slice 
     else: 
      raise TypeError('Index must be int, not {}'.format(type(key).__name__)) 

コードがisinstance()で明示的に引数の型をチェックします。これはPythonコミュニティ内のregarded as an antipatternです。どうすればそれを避けることができますか?

  • それは方法と互換性がないquite deliberatelyだので、私は(それは我々がすでにOOPの多型を経由してselfに派遣しているので、完全に無用である、selfに派遣しようとします)、functools.singledispatchを使用することはできません。 @staticmethodと動作しますが、selfのものを取り除く必要がある場合はどうすればよいですか?
  • int()にキャスティングしてTypeErrorをキャッチし、スライスを確認し、おそらく再発生させることはまだ醜いですが、多少はそうではありません。
  • 整数を1要素のスライスに変換し、同じコードで両方の状況を処理するのはきれいですが、それ自体の問題があります(0または[0]?を返す)。

答えて

13

奇妙に思えるくらい、あなたが持っている方法は、物事についての最良の方法だと思う。パターンは一般的に共通のユースケースを包含するために存在しますが、それは、それを追うと福音として受け止められなければならないという意味ではありません。 PEP 443が明示的な型チェックをしているのを防ぐための主な理由は、それが「脆弱で拡張に近い」ということです。ただし、これは主に、さまざまな種類のカスタム関数に適用されます。 Python docs on __getitem__から:

シーケンスタイプでは、受け入れられたキーは整数とスライスオブジェクトでなければなりません。負のインデックスの特別な解釈(クラスがシーケンス型をエミュレートしたい場合)は、__getitem __()メソッドまでです。 keyのタイプが不適切な場合、TypeErrorが発生する可能性があります。シーケンスの索引セット外の値(負の値を特別に解釈した後)の場合、IndexErrorを発生させる必要があります。マッピングタイプの場合、キーが見つからない場合(コンテナには含まれません)、KeyErrorを発生させる必要があります。

Pythonのドキュメントには、受け入れるべき2つの型と、その2つの型以外の項目がある場合の対処方法が明示されています。型がドキュメンテーション自体によって提供されていることを考えれば、変更することはほとんどありません(あなたの実装よりもはるかに多くの実装を破る)

明示的な型検査を避けるように設定されている場合は、this SO answerを指します。これはargs[0](自己)ではなく、args[1](arg)をチェックすることで、@singledispatchがメソッドを使用できるようにする@methdispatchデコレータの簡略化された実装です(私の名前ではありません。これを使用すると、__getitem__メソッドでカスタムシングルディスパッチを使用できるようになります。

これらの「pythonic」を考慮するかどうかはあなた次第ですが、Zen of Pythonでは「特別なケースはルールを破るほど特別ではない」と書いていますが、実用性は純粋さに打ち勝つ "。この場合、ドキュメントが明示的に述べている2つのタイプをチェックするだけで、__getitem__がサポートすべき唯一のものが私にとって実用的な方法であるように思えます。

0

私はそれを避ける方法を知らないよ。これは、このように動的に型定義された言語を使用することの単なるトレードオフです。しかし、それは何度も何度も何度もやらなければならないというわけではありません。私は、アウト分割メソッド名と抽象クラスを作成することにより、一度それを解決するため、その後Sequenceから直接そのクラスから継承する代わりに、たい:

class UnannoyingSequence(collections.abc.Sequence): 

    def __getitem__(self, key): 
     if isinstance(key, int): 
      return self.getitem(key) 
     elif isinstance(key, slice): 
      return self.getslice(key) 
     else: 
      raise TypeError('Index must be int, not {}'.format(type(key).__name__)) 

    # default implementation in terms of getitem 
    def getslice(self, key): 
     # Get a whole slice 

class FooSequence(UnannoyingSequence): 
    def getitem(self, key): 
     # Get a single item 

    # optional efficient, type-specific implementation not in terms of getitem 
    def getslice(self, key): 
     # Get a whole slice 

これがあれば、私もそれをこのように行う可能性があることを十分にFooSequenceをクリーンアップ私は唯一の派生クラスを持っていた。私は、標準ライブラリがすでにそのように動作していないことに驚いています。

+1

言うまでもなく、*私たちは '__getslice__'でこれを行うために*使われていますが、これは廃止されました。これは技術全体の健全性に疑問を感じます。 – Kevin

+0

@ケビン、それは異なる理由で廃止されました。彼らは 'slice'オブジェクトを作成したかったのですが、' __getslice__'は 'i'と' j'引数をとりました。下位互換性が壊れていました。 –

-1

pythonicを維持するには、オブジェクトの種類ではなくセマンティクスを使用します。したがって、シーケンスへのアクセサーとしていくつかのパラメーターがある場合は、そのように使用してください。可能な限り長い間、パラメータの抽象化を使用します。ユーザー識別子のセットが必要な場合は、セットが必要ではなく、むしろメソッドaddを持つデータ構造が必要です。テキストが必要な場合は、unicodeオブジェクトが必要ではなく、encodedecodeのメソッドの文字のコンテナが必要です。

一般的には、「特別な値が指定されていない限り、基本実装の動作を使用する」と仮定します。__getitem__を実装する場合は、特別な値、あなたは、単一の値(perlの用語では「スカラー」)を区別したい場合は

class FooSequence(collections.abc.Sequence): 
    # Snip other methods 

    def __getitem__(self, key): 
     try: 
      if key == SPECIAL_VALUE: 
       return SOMETHING_SPECIAL 
      else: 
       return self.our_baseclass_instance[key] 
     except AttributeError: 
      raise TypeError('Wrong type: {}'.format(type(key).__name__)) 

、シーケンス(Javaの用語では「コレクション」)、それは次のようになります。私は、次のパターンを使用したい提供されイテレータが実装されているかどうかを判断するためにはpythonでうまくいきます。今のようにtry-catchパターンまたは hasattrを使うことができます:

私たちの例に適用
>>> a = 42 
>>> b = [1, 3, 5, 7] 
>>> c = slice(1, 42) 
>>> hasattr(a, "__iter__") 
False 
>>> hasattr(b, "__iter__") 
True 
>>> hasattr(c, "__iter__") 
False 
>>> 

class FooSequence(collections.abc.Sequence): 
    # Snip other methods 

    def __getitem__(self, key): 
     try: 
      if hasattr(key, "__iter__"): 
       return map(lambda x: WHATEVER(x), key) 
      else: 
       return self.our_baseclass_instance[key] 
     except AttributeError: 
      raise TypeError('Wrong type: {}'.format(type(key).__name__)) 

ダイナミックPythonとRubyの利用ダックタイピングのようなプログラミング言語。そして、アヒルはアヒルのように歩き、アヒルのように泳ぎ、アヒルのように突き刺す動物です。誰かがそれを「アヒル」と呼ぶからではありません。

+2

私は同意しますが、getitemのドキュメントでは「int型とスライス型のオブジェクトのみを許可します」と明示的に述べています。 getitemがiterablesを受け入れることを想定していないという簡単な理由のためにiterablesを許すか受け入れる理由はなく、 '[] '演算子の目的のためにリストは決して提供されません。ダックタイピングはすべてうまくいっていますが、ドキュメントは受け入れる項目を正確に明示しています。詳細は、[python docs for getitem](https://docs.python.org/3.4/reference/datamodel.html#object.__getitem__)を参照してください。 –

+0

私は基本的な実装を持っていません。私は手ですべてをやっている。解説のためにコードは簡略化されています。私の実際のコードは3Dスライス可能であり、NumPyを使用せず、かなりの量のインデックス前処理が必要です。 – Kevin

+0

その場合、私は@ SevenDeadlySinsの答えを最も有用なものと考えます。 – meisterluk

関連する問題