クラスメソッドでsuperを使用する
-
08-07-2019 - |
質問
Pythonでsuper()関数を学習しようとしています。
この例(2.6)に出会って自分が動けなくなるまで、把握できたと思いました。
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "test.py", line 9, in do_something
do_something = classmethod(do_something)
TypeError: unbound method do_something() must be called with B instance as first argument (got nothing instead)
>>>
例の直前にこの行を読んだとき、私は期待していなかった:
クラスメソッドを使用している場合、superを呼び出すインスタンスはありません。幸いなことに、superは2番目の引数として型を使用しても機能します。 ---以下に示すように、タイプを直接superに渡すことができます。
これは、Pythonがdo_something()をBのインスタンスで呼び出すべきだと言って不可能だと言っていることです。
解決
テキストは、詳細ではなく、アイデアのフレーバーにより読まなければならない場合があります。これはそのようなケースの1つです。
リンクページの例2.5 、2.6および2.7はすべて1つのメソッド do_your_stuff
を使用する必要があります。 (つまり、 do_something
を do_your_stuff
に変更する必要があります。)
さらに、 Ned Deilyが指摘したように、 A.do_your_stuff
はクラスメソッド。
class A(object):
@classmethod
def do_your_stuff(cls):
print 'This is A'
class B(A):
@classmethod
def do_your_stuff(cls):
super(B, cls).do_your_stuff()
B.do_your_stuff()
super(B、cls).do_your_stuff
bound メソッドを返します( footnoteを参照してください2 )。 cls
は super()
の2番目の引数として渡されたため、返されたメソッドにバインドされるのは cls
です。つまり、 cls
はクラスAのメソッド do_your_stuff()
の最初の引数として渡されます。
繰り返します: super(B、cls).do_your_stuff()
は、 A
の do_your_stuff
メソッドを
cls
を最初の引数として渡して呼び出します。それが機能するためには、 A
do_your_stuff
はクラスメソッドでなければなりません。リンクされたページではそれについて言及されていませんが、
しかし、それは間違いなくそうです。
PS。 do_something = classmethod(do_something)
は、クラスメソッドを作成する古い方法です。
新しい(er)方法は@classmethodデコレータを使用することです。
super(B、cls)
は super(cls、cls)
に置き換えることはできません。そうすると、無限ループにつながる可能性があります。たとえば、
class A(object):
@classmethod
def do_your_stuff(cls):
print('This is A')
class B(A):
@classmethod
def do_your_stuff(cls):
print('This is B')
# super(B, cls).do_your_stuff() # CORRECT
super(cls, cls).do_your_stuff() # WRONG
class C(B):
@classmethod
def do_your_stuff(cls):
print('This is C')
# super(C, cls).do_your_stuff() # CORRECT
super(cls, cls).do_your_stuff() # WRONG
C.do_your_stuff()
RuntimeErrorが発生します:Pythonオブジェクトの呼び出し中に最大再帰深度を超えました。
cls
が C
の場合、 super(cls、cls)
は C.mro()
を検索します C
の後にくるクラス。
In [161]: C.mro()
Out[161]: [__main__.C, __main__.B, __main__.A, object]
そのクラスは B
であるため、 cls
が C
の場合、 super(cls、cls).do_your_stuff()
always は B.do_your_stuff
を呼び出します。 super(cls、cls).do_your_stuff()
は B.do_your_stuff
内で呼び出されるため、無限ループで B.do_your_stuff
を呼び出すことになります。
Python3では、 super の0引数形式
が追加され、Python3は super(B、cls)
を super()
に置き換えることができるようにcode> super( )
は、 class B
の定義で super(B、cls)
と同等である必要があります。
ただし、いかなる状況でも super(cls、cls)
(または同様の理由で、 super(type(self)、self)
)は常に正しいです。
他のヒント
Python 3では、 super
の引数の指定をスキップできます。
class A:
@classmethod
def f(cls):
return "A's f was called."
class B(A):
@classmethod
def f(cls):
return super().f()
assert B.f() == "A's f was called."
記事を少しわかりやすくするために更新しました: Pythonの属性とメソッド#スーパー
上記のclassmethodを使用した例は、クラスメソッドとは何かを示しています。最初のパラメーターとしてインスタンスではなくクラス自体を渡します。ただし、メソッドを呼び出すためのインスタンスさえ必要ありません。例:
>>> class A(object):
... @classmethod
... def foo(cls):
... print cls
...
>>> A.foo() # note this is called directly on the class
<class '__main__.A'>
Webページの例は、公開されたとおりに機能するようです。スーパークラス用に do_something
メソッドも作成しましたが、クラスメソッドにはしませんでしたか?このような何かがあなたにそのエラーを与えるでしょう:
>>> class A(object):
... def do_something(cls):
... print cls
... # do_something = classmethod(do_something)
...
>>> class B(A):
... def do_something(cls):
... super(B, cls).do_something()
... do_something = classmethod(do_something)
...
>>> B().do_something()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in do_something
TypeError: unbound method do_something() must be called with B instance as first argument (got nothing instead)
この美しいサイトと素敵なコミュニティのおかげで、ポイントを理解できたと思います。
気にしない場合、クラスメソッドが間違っている場合は修正してください(現在、完全に理解しようとしています):
# EXAMPLE #1
>>> class A(object):
... def foo(cls):
... print cls
... foo = classmethod(foo)
...
>>> a = A()
>>> a.foo()
# THIS IS THE CLASS ITSELF (__class__)
class '__main__.A'
# EXAMPLE #2
# SAME AS ABOVE (With new @decorator)
>>> class A(object):
... @classmethod
... def foo(cls):
... print cls
...
>>> a = A()
>>> a.foo()
class '__main__.A'
# EXAMPLE #3
>>> class B(object):
... def foo(self):
... print self
...
>>> b = B()
>>> b.foo()
# THIS IS THE INSTANCE WITH ADDRESS (self)
__main__.B object at 0xb747a8ec
>>>
この図が示すことを願っています。