instance.name
を介して(およびPython 2で)メソッドを検索するたびclass.name
に、メソッドオブジェクトが新しく作成されます。Pythonは記述子プロトコルを使用して、毎回メソッドオブジェクトで関数をラップします。
したがって、ルックアップするid(C.foo)
と、新しいメソッドオブジェクトが作成され、そのID(メモリアドレス)を取得してから、メソッドオブジェクトを再度破棄します。次に、を検索するid(cobj.foo)
と、解放されたメモリアドレスを再利用する新しいメソッドオブジェクトが作成され、同じ値が表示されます。その後、このメソッドは再び破棄されます(参照カウントが0に低下したときに収集されたガベージ)。
次に、C.foo
バインドされていないメソッドへの参照を変数に格納しました。これで、メモリアドレスは解放されず(参照カウントは0ではなく1になります)、新しいメモリ位置を使用する必要があるルックアップによって2番目のメソッドインスタンスを作成します。cobj.foo
したがって、2つの異なる値が得られます。
以下のドキュメントをid()
参照してください。
オブジェクトの「ID」を返します。これは整数(または長整数)であり、このオブジェクトの存続期間中、一意で一定であることが保証されています。ライフタイムが重複しない2つのオブジェクトは、同じid()
値を持つ場合があります。
CPython実装の詳細:これは、メモリ内のオブジェクトのアドレスです。
強調鉱山。
__dict__
クラスの属性を介して関数への直接参照を使用してメソッドを再作成してから、__get__
記述子メソッドを呼び出すことができます。
>>> class C(object):
... def foo(self):
... pass
...
>>> C.foo
<unbound method C.foo>
>>> C.__dict__['foo']
<function foo at 0x1088cc488>
>>> C.__dict__['foo'].__get__(None, C)
<unbound method C.foo>
>>> C.__dict__['foo'].__get__(C(), C)
<bound method C.foo of <__main__.C object at 0x1088d6f90>>
Python 3では、バインドされていないメソッドとバインドされているメソッドの区別がすべて削除されていることに注意してください。バインドされていないメソッドを取得する前に関数を取得し、それ以外の場合はメソッドを常にバインドする関数を取得します。
>>> C.foo
<function C.foo at 0x10bc48dd0>
>>> C.foo.__get__(None, C)
<function C.foo at 0x10bc48dd0>
>>> C.foo.__get__(C(), C)
<bound method C.foo of <__main__.C object at 0x10bc65150>>
さらに、Python 3.7は、毎回新しいメソッドオブジェクトを作成しないように、現在のオペコードペアを正確にLOAD_METHOD
置き換える新しいCALL_METHOD
オペコードペアを追加します。この最適化により、からの実行パスがwithで変換されるため、「手動で」インスタンスを関数オブジェクトに直接渡します。LOAD_ATTRIBUTE
CALL_FUNCTION
instance.foo()
type(instance).__dict__['foo'].__get__(instance, type(instance))()
type(instance).__dict__['foo'](instance)