Python reuses the same memory location as you hold no other references to the objects, once id(a.f)
is evaluated there are more references to the object so it is gc'd then python is free to reuse the same memory location for a.g
. if you assign the methods to names you will see different behaviour:
# creates a reference to the method f
In [190]: f = a.f
# creates a reference to the method g
In [191]: g = a.g
# cannot reuse the memory location of f as it is still referenced
In [192]: id(f) == id(g)
Out[192]: False
You actually really only need store a reference to f to see the same behaviour as above.
In [201]: f = a.f
In [202]: id(f) == id(a.g)
Out[202]: False
You can see the reference count with sys.getrefcount or gc.gc.get_referrers:
In [2]: import gc
In [3]: f = a.f
In [4]: len(gc.get_referrers(a.g)),len(gc.get_referrers(f))
Out[4]: (0, 1)
In [5]: sys.getrefcount(a.g),sys.getrefcount(f)
Out[5]: (1, 2)
The only reason you see 1 for a.g is because The count returned is generally one higher than you might expect, because it includes the (temporary) reference as an argument to getrefcount().
It is analogous to your own example, after the method is evaluated you will still have a reference to to f
, with a.g
the refcount would be 0 so it is immediately garbage collected and python is free to use the memory location for anything else.
It is also worth noting that the behaviour is not limited to methods but it is just a cpython implementation detail and not something that you should ever rely on:
In [67]: id([]), id([])
Out[67]: (139746946179848, 139746946179848)
In [73]: id(tuple()),id([]),id([])
Out[73]: (139747414818888, 139746946217544, 139746946217544)
In [74]: id([]),id([]),id([])
Out[74]: (139746946182024, 139746946182024, 139746946182024)
In [75]: id([]),id(tuple()),id([])
Out[75]: (139746946186888, 139747414818888, 139746946186888)
In [76]: id(tuple()),id([]),id(tuple())
Out[76]: (139747414818888, 139746946217736, 139747414818888)