24

次のスクリプト例を見てください。

class A(object):
    @classmethod
    def one(cls):
        print("I am class")

    @staticmethod
    def two():
        print("I am static")


class B(object):
    one = A.one
    two = A.two


B.one()
B.two()

このスクリプトを Python 2.7.11 で実行すると、次のようになります。

I am class
Traceback (most recent call last):
  File "test.py", line 17, in <module>
    B.two()
TypeError: unbound method two() must be called with B instance as first argument (got nothing instead)

@classmethod デコレータはクラス全体で保持されているようですが、 @staticmethod は保持されていません。

Python 3.4 は期待どおりに動作します。

I am class
I am static

Python2 が @staticmethod を保持しないのはなぜですか? 回避策はありますか?

編集:クラスから2つを取り出す(そして@staticmethodを保持する)ことはうまくいくようですが、それでも私には奇妙に思えます。

4

2 に答える 2

15

classmethodstaticmethodは記述子であり、どちらもあなたが期待することをしていませんstaticmethod

にアクセスするA.oneと、 にバインドされたメソッドが作成されA、それが の属性になりますがB、 にバインドされているためA、呼び出してもcls引数は常に になります(これは Python 2 と Python 3 の両方に当てはまります。どこでも間違っています)。 )。AB.one

にアクセスするA.twoと、生の関数オブジェクトが返されます (記述子は、 orstaticmethodを渡すバインディングを防止する以外に特別なことをする必要がないため、ラップしたものを返すだけです)。しかし、その生の関数オブジェクトは、バインドされていないインスタンス メソッドとしてアタッチされます。これは、ラッピングがなければ、通常どおりに定義したのと同じだからです。selfclsBstaticmethod

後者が Python 3 で機能する理由は、Python 3 にはバインドされていないメソッドの概念がないためです。これには関数 (クラスのインスタンスを介してアクセスするとバインドされたメソッドになる) とバインドされたメソッドがあり、Python 2 には関数、バインドされていないメソッド、バインドされたメソッドがあります。

バインドされていないメソッドは、正しいタイプのオブジェクトで呼び出されていることを確認するため、エラーになります。単純な関数は、正しい数の引数が必要なだけです。

Python 3のstaticmethodデコレーターはまだ生の関数オブジェクトを返しますが、Python 3 では問題ありません。これは特別なバインドされていないメソッド オブジェクトではないため、クラス自体で呼び出すと、メソッドではなく名前空間関数のようになります。次のことをしようとすると、問題が発生します。

B().two()

ただし、そのインスタンスBと関数からバインドされたメソッドが作成され、受け入れられないtwo追加の引数 ( self) が渡されるためです。two基本的に、Python 3 では、staticmethodバインディングを発生させずにインスタンスで関数を呼び出せるようにするのが便利ですが、クラス自体を参照してのみ関数を呼び出す場合は、Python 2 ではなく単なる関数であるため、必要ありません。 「アンバウンド法」。

このコピーを実行する何らかの理由があり (通常は から継承Aすることをお勧めしますが、何でも)、 でアクセスしたときに記述子が提供するものではなく、関数の記述子ラップ バージョンを確実に取得したい場合はA、に直接アクセスして、記述子プロトコルをバイパスしAます__dict__

class B(object):
    one = A.__dict__['one']
    two = A.__dict__['two']

クラス ディクショナリから直接コピーすることにより、記述子プロトコル マジックが呼び出されることはなく、 および のstaticmethodおよびclassmethodラップされたバージョンが得oneられますtwo

于 2016-12-02T17:25:38.423 に答える
4

免責事項:これは実際には答えではありませんが、コメント形式にも適合しません。

Python2 では、クラス間でも正しく@classmethod保持されないことに注意してください。以下のコードでは、 への呼び出しは、 class を介して呼び出されたかのように機能します。B.one()A

$ cat test.py 
class A(object):
    @classmethod
    def one(cls):
        print("I am class", cls.__name__)

class A2(A):
    pass

class B(object):
    one = A.one


A.one()
A2.one()
B.one()

$ python2 test.py 
('I am class', 'A')
('I am class', 'A2')
('I am class', 'A')
于 2016-12-02T17:08:55.633 に答える