1. ホーム
  2. python

[解決済み] Python 3.xのsuper()はなぜ魔法なのか?

2022-04-25 20:37:31

質問

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

これを実現するために、コンパイル時にいくつかのマジックが行われますが、その結果のひとつが、次のコード(これは supersuper_ ) は失敗します。

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で関数やメソッドなどを別の名前にバインドし直すと壊れるという例は他にもあるのでしょうか?

どのように解決するのですか?

新しい魔法 super() の挙動は、D.R.Y. (Don't Repeat Yourself) の原則に違反しないように追加されたものです。 PEP 3135 . グローバルに参照することで明示的にクラス名を付けなければならないことも、以下のような再バインドの問題を引き起こしがちです。 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はGuidoによってキックオフされました。 当初は super キーワードになる また、セルを使って現在のクラスを検索するというアイデアもあります。 も彼の . 確かに、キーワードにするアイデアは、その一部である PEPの最初のドラフト .

しかし、実際には、その後、グイド自身が キーワードのアイデアは、「魔法のようすぎる」として、このアイデアから離れました。 その代わりに、現在の実装を提案したのです。彼は を別の名前にすることが予想されました。 super() が問題になる可能性があります。 :

私のパッチは中間的な解決策を使用しています。 __class__ という名前の変数を使用する場合は、常に 'super' . したがって、もしあなたが (グローバルに) リネーム supersupper を使用し 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