8

useless_func次のコードで、が 2 つの異なるオブジェクトに属しているのに同じ id を持っている理由がわかりません。

class parent(object):
   @classmethod
   def a_class_method(cls):
     print "in class method %s" % cls

   @staticmethod
   def a_static_method():
     print "static method"

   def useless_func(self):
     pass

 p1, p2 = parent(),parent()

 id(p1) == id(p2) // False

 id(p1.useless_func) == id(p2.useless_func) // True
4

2 に答える 2

12

これは非常に興味深い質問です。

あなたの条件下では、それらは同じように見えます:

Python 2.7.2 (default, Oct 11 2012, 20:14:37) 
[GCC 4.2.1 Compatible Apple Clang 4.0 (tags/Apple/clang-418.0.60)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> class Foo(object):
...   def method(self): pass
... 
>>> a, b = Foo(), Foo()
>>> a.method == b.method
False
>>> id(a.method), id(b.method)
(4547151904, 4547151904)

ただし、それらを使用して何かを行うと、それらは異なるものになることに注意してください。

>>> a_m = a.method
>>> b_m = b.method
>>> id(a_m), id(b_m)
(4547151*9*04, 4547151*5*04)

そして、再度テストすると、再び変更されました!

>>> id(b.method)
4547304416
>>> id(a.method)
4547304416

インスタンス上のメソッドにアクセスすると、「バインドされたメソッド」のインスタンスが返されます。バインドされたメソッドは、インスタンスとメソッドの関数オブジェクトの両方への参照を格納します。

>>> a_m
<bound method Foo.method of <__main__.Foo object at 0x10f0e9a90>>
>>> a_m.im_func is Foo.__dict__['method']
True
>>> a_m.im_self is a
True

( 「バインドされていないメソッド」が生成されるためFoo.__dict__['method']、 ではなくを使用する必要があることに注意してください...その目的は、読者への演習として残されています)Foo.methodFoo.method

この「バインドされたメソッド」オブジェクトの目的は、メソッドが関数のように渡されるときに「適切に動作する」ようにすることです。たとえば、 functionを呼び出すと、明示的な参照がなくなったとしても、これはを呼び出すa_m()ことと同じです。この動作を JavaScript (たとえば) と比較してください。JavaScript では、 と同じ結果は得られませa.method()avar method = foo.method; method()foo.method()

それで!id(a.method)これにより、最初の質問に戻ります: がと同じ値を生成するように見えるのはなぜid(b.method)ですか? 私は Asad が正しいと信じています。それは Python の参照カウント ガベージ コレクターと関係があります*: 式id(a.method)が評価されると、バインドされたメソッドが割り当てられ、ID が計算され、バインドされたメソッドの割り当てが解除されます。次のバインドされたメソッド — for b.method— が割り当てられると、メモリ内のまったく同じ場所に割り当てられます。これは、バインドされたメソッド for が割り当てられてから割り当てが行われていない (またはバランスの取れた数の割り当てが行われていない) ためa.methodです。これは、a.methodが と同じメモリ位置を持っているように見えることを意味しますb.method

最後に、2 回目のチェックでメモリ位置が変更されたように見える理由を説明します。1 回目と 2 回目のチェックの間に行われた他の割り当ては、2 回目は別の場所に割り当てられていることを意味します (注:それらへのすべての参照が失われたため再割り当てされます; バインドされたメソッドはキャッシュされます†、同じメソッドに 2 回アクセスすると同じインスタンスが返されます: a_m0 = a.method; a_m1 = a.method; a_m0 is a_m1 => True)。

*: 衒学者の注: 実際には、これは実際のガベージ コレクターとは何の関係もありません。ガベージ コレクターは、循環参照を処理するためだけに存在します… しかし… それは別の日の話です。
†: 少なくとも CPython 2.7 では。CPython 2.6 はバインドされたメソッドをキャッシュしていないようです。そのため、動作が指定されていないことが予想されます。

于 2013-05-24T04:14:56.623 に答える
8

これが私が起こっていると思うことです:

  1. を逆参照するp1.useless_funcと、そのコピーがメモリ内に作成されます。このメモリ位置は、によって返されますid
  2. 作成したばかりのメソッドのコピーへの参照がないため、GC によって再利用され、メモリ アドレスが再び使用可能になります。
  3. を逆参照するp2.useless_funcと、そのコピーが同じメモリ アドレス (使用可能だった) に作成され、これを使用してid再度取得します。
  4. 2 番目のコピーは GCd です

他の多くのコードを実行してインスタンス メソッドの ID を再度確認すると、ids は互いに同一であるに違いありませんが、元の実行とは異なります。

idさらに、David Wolver の例では、メソッド copy への永続的な参照が取得されるとすぐにs が異なることに気付くかもしれません。

この理論を確認するために、CPython の参照カウント ガベージ コレクションを使用しない Jython を使用したシェル セッションを次に示します (PyPy と同じ結果)。

Jython 2.5.2 (Debian:hg/91332231a448, Jun 3 2012, 09:02:34) 
[OpenJDK Server VM (Oracle Corporation)] on java1.7.0_21
Type "help", "copyright", "credits" or "license" for more information.
>>> class parent(object):
...     def m(self):
...             pass
... 
>>> p1, p2 = parent(), parent()
>>> id(p1.m) == id(p2.m)
False
于 2013-05-24T04:22:41.480 に答える