なぜPython 3.xのsuper()マジックなのですか?


159

Python 3.xでは、super()引数なしで呼び出すことができます。

class A(object):
    def x(self):
         print("Hey now")

class B(A):
    def x(self):
        super().x()
>>> B().x()
Hey now

これを機能させるために、いくつかのコンパイル時の魔法が実行されます。その結果の1つは、次のコード(に再バインドさsuperれるsuper_)が失敗することです。

super_ = super

class A(object):
    def x(self):
        print("No flipping")

class B(A):
    def x(self):
        super_().x()
>>> B().x()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in x
RuntimeError: super(): __class__ cell not found

super()コンパイラーの支援なしに実行時にスーパークラスを解決できないのはなぜですか?この振る舞い、またはその根本的な理由が、不注意なプログラマを噛む可能性のある実際的な状況はありますか?

...そして副次的な質問として:関数、メソッドなどのPythonの他の例はありますか?


6
私はアーミンは、この上で説明をさせます1。これもまた良い投稿です
Games Brainiac

回答:


216

super()DRY(Do n't Repeat Yourself)の原則に違反しないように、新しい魔法の動作が追加されました。PEP3135を参照してください。クラスをグローバルとして参照することによってクラスに明示的に名前を付ける必要がある場合も、super()それ自体で発見したのと同じ再バインドの問題が発生しやすくなります。

class Foo(Bar):
    def baz(self):
        return super(Foo, self).baz() + 42

Spam = Foo
Foo = something_else()

Spam().baz()  # liable to blow up

同じことがクラスデコレータの使用にも当てはまり、デコレータはクラス名を再バインドする新しいオブジェクトを返します。

@class_decorator_returning_new_class
class Foo(Bar):
    def baz(self):
        # Now `Foo` is a *different class*
        return super(Foo, self).baz() + 42

マジックsuper() __class__セルは、元のクラスオブジェクトにアクセスできるようにすることで、これらの問題をうまく回避します。

PEPは、最初superはキーワードなることを構想していたグイドによって開始されました。現在のクラスを検索するためにセルを使用するという考えも彼のものでした。確かに、それをキーワードにするという考えは、PEPの最初の草案の一部でした。

しかし、実際にはキーワードのアイデアから「あまりにも不思議」離れ、現在の実装を提案したのは実際にはグイド自身でした。に別の名前を使用するとsuper()問題が発生する可能性があると予想していました

私のパッチは中間ソリューション__class__ を使用しています'super'。という名前の変数を使用する場合は常に必要であると想定しています。したがって、(全体的に)に名前superを変更しsupperて使用するsupperが、を使用しない場合super、引数なしでは機能しません(ただし、引数__class__または実際のクラスオブジェクトを渡すと機能し ます)という名前の無関係な変数がある場合、問題はありませんsuperが、メソッドはセル変数に使用されている少し遅い呼び出しパスを使用します。

結局、superキーワードを使用するのは正しくないと感じ、魔法の__class__セルを提供することは許容できる妥協であると宣言したのは、グイド自身でした。

実装の魔法の暗黙の動作はいくぶん驚くべきことですがsuper()、言語で最も不適切に適用された関数の1つであることに同意します。 インターネットで見つかった誤用super(type(self), self)super(self.__class__, self)呼び出しのすべてを見てください。そのコードのいずれかが派生クラスから呼び出された場合、無限再帰例外が発生します。少なくとも、super()引数なしの単純化された呼び出しは、その問題を回避ます。

名前が変更されたのはsuper_; __class__メソッドでも参照するだけで、再び機能します。あなたはどちらかの参照している場合、セルが作成されsuper たり __class__、あなたの方法で名前を:

>>> super_ = super
>>> class A(object):
...     def x(self):
...         print("No flipping")
... 
>>> class B(A):
...     def x(self):
...         __class__  # just referencing it is enough
...         super_().x()
... 
>>> B().x()
No flipping

1
良い記事。しかし、それでも泥と同じくらいはっきりしています。super()は、自動インスタンス化された関数のようなdef super(of_class=magic __class__)ものself.super(); def super(self): return self.__class__ですか?
Charles Merriam

17
@CharlesMerriam:この投稿は、super()引数なしでの動作に関するものではありません。それは主にそれが存在する理由を扱います。super()は、クラスメソッドでは、と同じsuper(ReferenceToClassMethodIsBeingDefinedIn, self)です。ReferenceToClassMethodIsBeingDefinedInは、コンパイル時に決定され__class__、と名付けられたクロージャーとしてメソッドにアタッチされ、super()実行時に呼び出しフレームから両方を検索します。しかし、実際にこれらすべてを知る必要はありません。
Martijn Pieters

1
@CharlesMerriam:しかしsuper()自動的にインスタンス化された関数に近いところはありません。
Martijn Pieters

1
@ chris.leonard:重要な文章は、super()を使用__class__するか、メソッドで使用すると、セルが作成されますsuper関数で名前を使用しました。コンパイラはそれを見て、__class__クロージャを追加します。
Martijn Pieters

4
@Alexey:それは十分ではありません。メソッドが定義されているタイプと同じではない現在のタイプtype(self)を示します。クラスだから、メソッドを持つ必要性は、としてそれはサブクラス化される可能性があるため、その時点で、あるとあなたに無限ループを与えるだろう。私の回答でリンクしている投稿を参照してください:派生クラスでsuper()を呼び出すときに、self .__ class__を渡すことができますか?Foobazsuper(Foo, self).baz()class Ham(Foo):type(self)Hamsuper(type(self), self).baz()
Martijn Pieters
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.