10

別の質問を調査したところ、次のことがわかりました。

>>> class A:
...   def m(self): return 42
... 
>>> a = A()

これは予想されていました:

>>> A.m == A.m
True
>>> a.m == a.m
True

しかし、これは私が期待していませんでした:

>>> a.m is a.m
False

特にこれではありません:

>>> A.m is A.m
False

Python は、メソッド アクセスごとに新しいオブジェクトを作成するようです。この動作が見られるのはなぜですか? つまり、クラスごとに 1 つのオブジェクトとインスタンスごとに 1 つのオブジェクトを再利用できない理由は何ですか?

4

2 に答える 2

14

はい、Python はアクセスごとに新しいメソッド オブジェクトを作成します。これは、渡すラッパー オブジェクトを構築するためselfです。これをバインド メソッドと呼びます。

Python はこれを行うために記述子を使用します。関数オブジェクトには__get__、クラスでアクセスされたときに呼び出されるメソッドがあります。

>>> A.__dict__['m'].__get__(A(), A)
<bound method A.m of <__main__.A object at 0x10c29bc10>>
>>> A().m
<bound method A.m of <__main__.A object at 0x10c3af450>>

Python は再利用できないことに注意してくださいA().m。Python は非常に動的な言語であり、アクセスするという行為自体がより多くのコードをトリガーする可能性があり、次にアクセスしたときに返さ.mれるものの動作が変わる可能性があります。A().m

@classmethodとデコレータはこの@staticmethodメカニズムを利用して、代わりにクラスにバインドされたメソッド オブジェクトと、バインドされていない単純な関数をそれぞれ返します。

>>> class Foo:
...     @classmethod
...     def bar(cls): pass
...     @staticmethod
...     def baz(): pass
... 
>>> Foo.__dict__['bar'].__get__(Foo(), Foo)
<bound method type.bar of <class '__main__.Foo'>>
>>> Foo.__dict__['baz'].__get__(Foo(), Foo)
<function Foo.baz at 0x10c2a1f80>
>>> Foo().bar
<bound method type.bar of <class '__main__.Foo'>>
>>> Foo().baz
<function Foo.baz at 0x10c2a1f80>

詳細については、Python 記述子のハウツーを参照してください。

ただし、Python 3.7 では、毎回新しいメソッド オブジェクトを作成することを避けるために、現在の- opcode ペアを正確に置き換える新しいLOAD_METHOD- opcode ペアが追加されています。この最適化は、 from withの実行パスを変換するため、「手動で」インスタンスを関数オブジェクトに直接渡します。見つかった属性が純粋な Python 関数オブジェクトでない場合、最適化は通常の属性アクセス パス (バインディング記述子を含む) に戻ります。CALL_METHODLOAD_ATTRIBUTECALL_FUNCTIONinstance.foo()type(instance).__dict__['foo'].__get__(instance, type(instance))()type(instance).__dict__['foo'](instance)

于 2014-01-08T17:22:20.300 に答える
7

これは、バインドされたメソッドを実装する最も便利で、魔法が少なく、スペース効率が最も高い方法だからです。

ご存じないかもしれませんが、バインドされたメソッドとは、次のようなことができることを指します。

f = obj.m
# ... in another place, at another time
f(args, but, not, self)

関数は記述子です。記述子は、クラスまたはオブジェクトの属性としてアクセスすると異なる動作をする一般的なオブジェクトです。propertyclassmethod、 、および他のいくつかのものを実装するために使用されstaticmethodます。関数記述子の特定の操作は、クラス アクセスに対して自身を返し、インスタンス アクセスに対して新たにバインドされたメソッド オブジェクトを返すことです。(実際には、これは Python 3 にのみ当てはまります。Python 2 はこの点でより複雑です。基本的に関数であるが完全ではない「バインドされていないメソッド」があります)。

アクセスごとに新しいオブジェクトが作成される理由は、単純さと効率性のためです。すべてのインスタンスのすべてのメソッドに対して事前にバインドされたメソッドを作成するには、時間とスペースが必要です。それらをオンデマンドで作成し、決して解放しないと、潜在的なメモリ リークが発生し (ただし、CPython は他の組み込み型に対して同様のことを行います)、場合によってはわずかに遅くなります。複雑な weakref ベースのキャッシング スキーム メソッド オブジェクトも無料ではなく、はるかに複雑です (歴史的に、バインドされたメソッドは、weakref よりはるかに前から存在します)。

于 2014-01-08T17:27:24.523 に答える