webdevqa.jp.net

クラスメソッドでsuperを使用する

Pythonでsuper()関数を学ぼうとしています。

この例(2.6)に出くわして自分が動けなくなるまで、私はそれを把握していると思った。

http://www.cafepy.com/article/python_attributes_and_methods/python_attributes_and_methods.html#super-with-classmethod-example

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のインスタンスで呼び出されるべきだと言うことでは不可能だと言っています。

65
dezza

場合によっては、詳細よりもアイデアのフレーバーのために、テキストをもっと読む必要があります。これはそのようなケースの1つです。

linked page では、例2.5、2.6および2.7はすべて_do_your_stuff_という1つのメソッドを使用する必要があります。 (つまり、_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_stuffboundメソッドを返します( footnote 2 を参照)。 clssuper()の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)は、クラスメソッドを作成する古い方法です。新しい方法は、@ 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: maximum recursion depth exceeded while calling a Python object_を発生させます。

clsCの場合、super(cls, cls)C.mro()Cの後にあるクラスを検索します。

_In [161]: C.mro()
Out[161]: [__main__.C, __main__.B, __main__.A, object]
_

そのクラスはBであるため、clsCの場合、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では、 0-argument形式のsuper が追加されたため、super(B, cls)super()に置き換えることができ、Python3は_class B_の定義のsuper()super(B, cls)と同等である必要があるというコンテキスト。

ただし、super(cls, cls)(または同様の理由でsuper(type(self), self))が正しいという状況はありません。

80
unutbu

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."
14
Tankobot

記事を少しわかりやすくするために更新しました: Pythonの属性とメソッド#スーパー

上記のclassmethodを使用した例は、クラスメソッドとは何かを示しています-インスタンスの代わりにクラス自体を最初のパラメーターとして渡します。しかし、メソッドを呼び出すためのインスタンスさえ必要ありません、例えば:

>>> class A(object):
...     @classmethod
...     def foo(cls):
...         print cls
... 
>>> A.foo() # note this is called directly on the class
<class '__main__.A'>
3
Shalabh

この美しいサイトと素敵なコミュニティのおかげで、私は今ポイントを理解したと思います。

あなたが気にしない場合は、私がクラスメソッドについて間違っている場合は私を修正してください(今私は完全に理解しようとしています):


# 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
>>>

この図が示すことを願っています..

0
dezza