16

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
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Timur
  • 295
  • 2
  • 12

4 Answers4

15

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)
Padraic Cunningham
  • 176,452
  • 29
  • 245
  • 321
4

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.

  1. When you grubbing id of a.f, a copy of it is created in memory. This memory location is returned by id.
  2. Since there are no references to the newly created method, it reclaimed by the GC (now memory address is available again).
  3. After you getting id of a.g, a copy of it is created at the same memory address, which you retrieve using id again.
  4. You've got truthy id's comparison.

Good luck!

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Andriy Ivaneyko
  • 20,639
  • 6
  • 60
  • 82
2

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
2

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
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Mazdak
  • 105,000
  • 18
  • 159
  • 188