31

バインドされたメソッドとバインドされていないメソッドに関するコードをいくつか試しました。それらを呼び出すと、両方ともオブジェクトを返すと思います。しかし、id()情報を取得するために使用すると、理解できないものが返されます。

IDE:Eclipse

プラグイン:pydev

Class C(object):
    def foo(self):
        pass

cobj = C()

print id(C.foo)    #1
print id(cobj.foo) #2

a = C.foo
b = cobj.foo

print id(a)        #3
print id(b)        #4

そして出力は...

5671672

5671672

5671672

5669368

#1と#2が同じIDを返すのはなぜですか?それらは異なるオブジェクトではありませんか?そして、とを2つの変数に割り当てるC.fooconj.foo、#3と#4は異なるIDを返します。

#3と#4は同じオブジェクトではなく、#1と#2を示していると思います...

バインドされたメソッドのIDとバインドされていないメソッドのIDの違いは何ですか?

4

2 に答える 2

51

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_ATTRIBUTECALL_FUNCTIONinstance.foo()type(instance).__dict__['foo'].__get__(instance, type(instance))()type(instance).__dict__['foo'](instance)

于 2012-11-12T17:12:11.760 に答える
9

@Martijn Pietersの非常に良い答えに追加:

In [1]: class C(object):
   ...:     def foo(self):
   ...:         pass
   ...:

In [2]: c = C()

In [3]: id(c.foo), id(C.foo)
Out[3]: (149751844, 149751844)  # so 149751844 is current free memory address

In [4]: a = c.foo  # now 149751844 is assigned to a

In [5]: id(a)              
Out[5]: 149751844

# now python will allocate some different address to c.foo and C.foo     

In [6]: id(c.foo), id(C.foo)    # different address used this time, and
Out[6]: (149752284, 149752284)  # that address is freed after this step

# now 149752284 is again free, as it was not allocated to any variable

In [7]: b = C.foo  # now 149752284 is allocated to b    

In [8]: id(b)
Out[8]: 149752284                

In [9]: c.foo is C.foo  # better use `is` to compare objects, rather than id()
Out[9]: False
于 2012-11-12T17:20:19.167 に答える