Pythonが「マジックメソッド」を使用する理由


99

私は最近Pythonをいじっていますが、少し奇妙だと思うのは、「マジックメソッド」の広範な使用です。たとえば、その長さを利用できるようにしたり、オブジェクトがメソッドを実装したりしてdef __len__(self)、次のときに呼び出されます。あなたが書くlen(obj)

なぜオブジェクトは単にlen(self)メソッドを定義せず、オブジェクトのメンバーとして直接呼び出されるのか疑問に思っていましたobj.len()。Pythonがそのようにそれを行うには十分な理由があるに違いないと私は確信しているが、初心者として、私はそれらが何であるかをまだ解明していない。


4
私は一般的な理由のような)歴史的およびb)何かだと思うlen()reversed()、さまざまなタイプのオブジェクトに適用されるが、そのような方法としてappend()のみなど、シーケンスに適用される
グラントポール

回答:


64

私の知る限り、lenこの点で特別であり、歴史的なルーツがあります。

ここにFAQからの引用があります:

Pythonが一部の機能(list.index()など)にメソッドを使用し、他の機能(len(list)など)に関数を使用するのはなぜですか?

主な理由は歴史です。関数は、タイプのグループに対して一般的であり、メソッドをまったく持たないオブジェクト(タプルなど)でも機能することを目的とした操作に使用されました。また、Pythonの機能機能(map()、apply()など)を使用するときに、アモルファスオブジェクトのコレクションに簡単に適用できる関数があると便利です。

実際、組み込み関数としてlen()、max()、min()を実装する方が、実際には、各タイプのメソッドとして実装するよりもコードが少なくて済みます。個々のケースについて疑問を投げかけることはできますが、これはPythonの一部であり、そのような根本的な変更を今行うには遅すぎます。関数は、大規模なコード破損を回避するために残しておく必要があります。

他の「魔法の方法」(実際にはPythonの民間伝承では特別な方法と呼ばれています)は多くの意味があり、他の言語にも同様の機能があります。これらは主に、特別な構文が使用されたときに暗黙的に呼び出されるコードに使用されます。

例えば:

  • オーバーロードされた演算子(C ++などに存在)
  • コンストラクタ/デストラクタ
  • 属性にアクセスするためのフック
  • メタプログラミングのためのツール

等々...


2
Pythonと最小驚きの原則は、Pythonがこのようにあることの利点のいくつかを読むのに適しています(ただし、英語が必要であることは認めます)。基本的なポイント:これにより、標準ライブラリが大量のコードを実装できるようになり、非常に、非常に再利用可能になりますが、オーバーライド可能になります。
jpmc26 2014年

20

Zen of Pythonから:

あいまいな状況に直面して、推測する誘惑を拒否してください。
それを行うには、明白な方法が1つ(できれば1つだけ)あるはずです。

カスタムメソッドで、開発者のような、別のメソッド名を自由に選択するだろう-これが理由の一つであるgetLength()length()getlength()または全く。Pythonでは、共通の関数len()を使用できるように厳密な名前が付けられています。

多くのタイプのオブジェクトに共通するすべての操作は__nonzero____len__やのような魔法のメソッドに入れられ__repr__ます。ただし、それらはほとんどオプションです。

演算子のオーバーロードはマジックメソッド(など__le__)でも行われるため、他の一般的な操作にも使用するのは理にかなっています。


これは説得力のある議論です。「Guidoは実際にはOOを信じていなかった」ことをもっと満足させます。
アンディヘイデン

15

Pythonは「マジックメソッド」という単語を使用します。これらのメソッドは、プログラムに対して実際にマジックを実行するためです。Pythonのマジックメソッドを使用する最大の利点の1つは、オブジェクトを組み込み型のように動作させる簡単な方法を提供することです。つまり、基本的な演算子を実行する醜い、直感に反する、非標準的な方法を回避できるということです。

次の例について考えてみます。

dict1 = {1 : "ABC"}
dict2 = {2 : "EFG"}

dict1 + dict2
Traceback (most recent call last):
  File "python", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'dict' and 'dict'

辞書タイプは追加をサポートしていないため、これはエラーになります。次に、辞書クラスを拡張して、「__ add__」マジックメソッドを追加します。

class AddableDict(dict):

    def __add__(self, otherObj):
        self.update(otherObj)
        return AddableDict(self)


dict1 = AddableDict({1 : "ABC"})
dict2 = AddableDict({2 : "EFG"})

print (dict1 + dict2)

これで、次の出力が得られます。

{1: 'ABC', 2: 'EFG'}

したがって、このメソッドを追加することで、突然マジックが発生し、以前に発生していたエラーはなくなりました。

私はそれがあなたに物事を明確にすることを願っています。詳細については、以下を参照してください。

Pythonのマジックメソッドのガイド(Rafe Kettler、2012年)


9

これらの関数の一部は、(スーパークラスの抽象メソッドなしで)実装できる単一のメソッドを超えるものを実行します。たとえばbool()、次のように動作します。

def bool(obj):
    if hasattr(obj, '__nonzero__'):
        return bool(obj.__nonzero__())
    elif hasattr(obj, '__len__'):
        if obj.__len__():
            return True
        else:
            return False
    return True

また、bool()常にTrueまたはFalseを返すことを100%確信できます。メソッドに依存している場合、何が返されるかを完全に確信することはできません。

比較的複雑な実装を持つ他のいくつかの関数(基礎となるマジックメソッドよりも複雑である可能性が高い)はiter()、およびcmp()、およびすべての属性メソッド(getattrsetattrおよびdelattr)です。のようなものは、int強制を行うときにマジックメソッドにアクセスします(実装できます__int__)が、型として二重の役割を果たします。 len(obj)実際には、とはまったく異なるとは思えない1つのケースobj.__len__()です。


2
代わりにhasattr()私が使用するtry:/ except AttributeError:し、代わりのをif obj.__len__(): return True else: return False私は言うだろうreturn obj.__len__() > 0が、それらは単なる文体ものです。
Chris Lutz

Python 2.6(これはbool(x)と呼ばれx.__nonzero__()ます)では、このメソッドは機能しません。boolインスタンスにはmethod __nonzero__()があり、objがboolになると、コードはそれ自体を呼び出し続けます。おそらくbool(obj.__bool__())、あなたが扱ったのと同じように扱われるべき__len__ですか?(または、このコードは実際にPython 3で機能しますか?)
Ponkadoodle 2010

bool()の循環的な性質は、定義の特異な循環的な性質を反映するために、意図的に少しばかげたものでした。それは単に原始的であると考えられるべきであるという議論があります。
Ian Bicking 2010

唯一の違い(現在)len(x)x.__len__()、前者を超え長さについてOverflowErrorを上げることであるsys.maxsize後者は、一般的にPythonで実装タイプのためではないだろうが、。ただし、これは機能よりもバグです(たとえば、Python 3.2のrangeオブジェクトは、主に任意の大きな範囲を処理できますがlen、それらを使用すると失敗する可能性があります__len__
。Python

4

それらは実際には「魔法の名前」ではありません。これは、特定のサービスを提供するためにオブジェクトが実装しなければならないインターフェースにすぎません。この意味で、これらは、再実装する必要のある定義済みのインターフェース定義よりも魔法ではありません。


1

その理由は主に歴史的なものですが、Python lenには、メソッドではなく関数を使用するのに適したいくつかの特徴があります。

Pythonでいくつかの動作は、例えば、メソッドとして実装されているlist.indexdict.append、他のものは、例えば、呼び出し可能オブジェクトとマジックメソッドとして実装されている間striterし、reversed。2つのグループは十分に異なるので、異なるアプローチが正当化されます。

  1. 彼らは一般的です。
  2. strintおよび友達はタイプです。コンストラクタを呼び出す方が理にかなっています。
  3. 実装は関数呼び出しとは異なります。たとえば、iterが利用できない__getitem__場合に呼び出さ__iter__れ、メソッド呼び出しに適合しない追加の引数をサポートします。同じ理由で、最近のバージョンのPythonではにit.next()変更さnext(it)れています。
  4. これらのいくつかは、演算子の近縁です。呼び出すためのそこの構文__iter____next__それが呼ばれています- forループ。一貫性を保つために、関数の方が優れています。そして、それは特定の最適化にとってそれをより良くします。
  5. 機能のいくつかは、単にいくつかの方法で、残りの部分にあまりにも似ている- reprのように動作しstrません。持つstr(x)対は、x.repr()混乱を招くことになります。
  6. たとえば、実際の実装方法をほとんど使用しないものもありますisinstance
  7. それらのいくつかは、実際の演算子である、getattr(x, 'a')ことのもう一つの方法であるx.agetattr、前述の資質の多くを共有します。

私は個人的に、最初のグループをメソッドのように、2番目のグループをオペレーターのように呼んでいます。それはあまり良い区別ではありませんが、何らかの形で役立つことを願っています。

これを言っても、len2番目のグループには完全には適合しません。それは最初のものの操作により近いですが、唯一の違いはほとんどすべての操作よりも一般的であることです。しかし、それが行う唯一のことはを呼び出すこと__len__であり、それはに非常に近いL.indexです。ただし、いくつかの違いがあります。たとえば__len__、などの他の機能の実装のためboolに呼び出されるlen場合があります。メソッドが呼び出された場合、まったく異なる動作bool(x)をするカスタムlenメソッドで中断する可能性があります。

つまり、クラスが実装する可能性のある非常に一般的な機能のセットがあり、これらは、演算子、特別な関数(通常は演算子のように実装よりも多くの機能を果たす)、オブジェクトの構築中、およびそれらすべてにアクセスできます。いくつかの共通の特徴を共有します。残りはすべてメソッドです。そしてlen、そのルールのいくぶん例外です。


0

上記の2つの投稿に追加することは多くありませんが、すべての「マジック」関数は実際にはまったくマジックではありません。これらは、インタプリタの起動時に暗黙的/自動的にインポートされる__ builtins__モジュールの一部です。すなわち:

from __builtins__ import *

プログラムが開始する前に毎回発生します。

Pythonがインタラクティブシェルに対してのみこれを実行し、必要なビルトインからさまざまなパーツをインポートするためのスクリプトが必要な場合は、常にもっと正しいと思っていました。また、おそらく異なる__ main__処理は、シェルと対話型では良いでしょう。とにかく、すべての機能をチェックして、それらがない場合の状態を確認してください。

dir (__builtins__)
...
del __builtins__
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.