I thought that is
operator checks the objects id
's equality. But it doesn't seem so:
>>> class A(object):
... def f(): return 1
... def g(): return 2
...
>>> a = A()
>>> a.f is a.g
False
>>> id(a.f) == id(a.g)
True
I thought that is
operator checks the objects id
's equality. But it doesn't seem so:
>>> class A(object):
... def f(): return 1
... def g(): return 2
...
>>> a = A()
>>> a.f is a.g
False
>>> id(a.f) == id(a.g)
True
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)
The same memory location is being used by Python for methods a.f
and a.g
, which are **two objects with non-overlapping lifetimes*, so id
returns same identity for both of them. See more detailed explanations below.
From the documentation for the is operator:
The operators is and is not test for object identity: x is y is true if and only if x and y are the same object.
From the documentation for the is id
Return the “identity” of an object. This is an integer (or long integer) which is guaranteed to be unique and constant for this object during its lifetime. Two objects with non-overlapping lifetimes may have the same id() value.
Explanations:
Whenever you look up a method via class.name
or instance.name
, the method object is created a-new. Python uses the descriptor protocol to wrap the function in a method object each time.
So, when you look up id(a.f)
or id(a.g)
, a new method object is created.
a.f
, a copy of it is created in memory. This memory location is returned by id
.a.g
, a copy of it is created at the same memory address, which you retrieve using id
again.Good luck!
a.f
and a.g are different objects.
The is
operator only returns true when there is one object.
But two objects with non-overlapping lifetimes may have the same id() value.
Refer to here for the id operator.
The operator is
checks the object identity, not value. In this case you have two separate functions (objects); thus they have a different identity.
And about the following part:
>>> id(a.f) == id(a.g)
True
Since Python creates the objects at run time, the first time Python attempts to get the id of a.f
, the a.g
hasn't been defined and based on the Python wiki Two objects with non-overlapping lifetimes may have the same id() value.
So in this case objects a.f
and a.g
that have non-overlapping lifetimes have equal id.
Return the “identity” of an object. This is an integer (or long integer) which is guaranteed to be unique and constant for this object during its lifetime. Two objects with non-overlapping lifetimes may have the same id() value.
Some extra notes about is
operator:
As I said in aforementioned lines the is
operator will check the identity of objects and object in Python will be created at run time. But this is not true for some small types like integers and strings, because they are singletons and not Python objects. Hence they will be located right away in memory like C types.
For better demonstration you can see the following examples:
>>> 100 is 10*10
True
>>>
>>> 1000 is 10*100
False
>>>
And for strings:
>>> 'aaaa'*5 is 'a'*20
True
>>> 'aaaa'*50 is 'a'*200
False