This is a very interesting question!
Under your conditions, they do appear the same:
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)
However, notice that once you do anything with them, they become different:
>>> a_m = a.method
>>> b_m = b.method
>>> id(a_m), id(b_m)
(4547151*9*04, 4547151*5*04)
And then, when tested again, they have changed again!
>>> id(b.method)
4547304416
>>> id(a.method)
4547304416
When a method on an instance is accessed, an instance of "bound method" is returned. A bound method stores a reference to both the instance and to the method's function object:
>>> 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
(note that I need to use Foo.__dict__['method']
, not Foo.method
, because Foo.method
will yield an "unbound method"… the purpose of which is left as an exercise to the reader)
The purpose of this "bound method" object is to make methods "behave sensibly" when they are passed around like functions. For example, when I call function a_m()
, that is identical to calling a.method()
, even though we don't have an explicit reference to a
any more. Contrast this behaviour with JavaScript (for example), where var method = foo.method; method()
does not produce the same result as foo.method()
.
SO! This brings us back to the initial question: why does it seem that id(a.method)
yields the same value as id(b.method)
? I believe that Asad is correct: it has to do with Python's reference-counting garbage collector*: when the expression id(a.method)
is evaluated, a bound method is allocated, the ID is computed, and the bound method is deallocated. When the next bound method — for b.method
— is allocated, it is allocated to exactly the same location in memory, since there haven't been any (or have been a balanced number of) allocations since the bound method for a.method
was allocated. This means that a.method
appears to have the same memory location as b.method
.
Finally, this explains why the memory locations appear to change the second time they are checked: the other allocations which have taken place between the first and the second check mean that, the second time, they are allocated at a different location (note: they are re-allocated because all references to them were lost; bound methods are cached?, so accessing the same method twice will return the same instance: a_m0 = a.method; a_m1 = a.method; a_m0 is a_m1 => True
).
*: pedants note: actually, this has nothing to do with the actual garbage collector, which only exists to deal with circular references… but… that's a story for another day.
?: at least in CPython 2.7; CPython 2.6 doesn't seem to cache bound methods, which would lead me to expect that the behaviour isn't specified.