12

The results from the code below in Python 2.7 struck me as a contradiction. The is operator is supposed to work with object identity and so is id. But their results diverge when I'm looking at a user-defined method. Why is that?

py-mach >>class Hello(object):
...  def hello():
...    pass
...
py-mach >>Hello.hello is Hello.hello
False
py-mach >>id(Hello.hello) - id(Hello.hello)
0

I found the following excerpt from the description of the Python data model somewhat useful. But it didn't really make everything clear. Why does the id function return the same integer if the user-defined method objects are constructed anew each time?

User-defined method objects may be created when getting an attribute of a class (perhaps via an instance of that class), if that attribute is a user-defined function object, an unbound user-defined method object, or a class method object. When the attribute is a user-defined method object, a new method object is only created if the class from which it is being retrieved is the same as, or a derived class of, the class stored in the original method object; otherwise, the original method object is used as it is.

gkb0986
  • 3,099
  • 1
  • 24
  • 22
  • @user2357112 -- I think OP understands that part "Why does the `id` function return the same integer if the user-defined method objects are constructed anew each time?". A better dupe would be http://stackoverflow.com/q/3877230/748858 – mgilson Sep 15 '14 at 15:08
  • @mgilson: Yep, which is why I reopened. I need to stop using the dupehammer so hastily. – user2357112 Sep 15 '14 at 15:12
  • @user2357112 -- I kinda liked it better back in the day when it required 4 more people to agree with me that it was a dupe -- These days I'm more reluctant to bring the dupehammer down... – mgilson Sep 15 '14 at 15:15

2 Answers2

20

The Python documentation for the id function states:

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.

(emphasis mine)

When you do id(Hello.hello) == id(Hello.hello), the method object is created only briefly and is considered "dead" after the first call to 'id'. Because of the call to id, you only need Hello.hello to be alive for a short period of time -- enough to obtain the id. Once you get that id, the object is dead and the second Hello.hello can reuse that address, which makes it appear as if the two objects have the same id.

This is in contrast to doing Hello.hello is Hello.hello -- both instances have to live long enough to be compared to each other, so you end up having two live instances.

If you instead tried:

>>> a = Hello.hello
>>> b = Hello.hello
>>> id(a) == id(b)
False

...you'd get the expected value of False.

Michael0x2a
  • 58,192
  • 30
  • 175
  • 224
  • 1
    In theory `Hello.hello is Hello.hello` could delete its reference of the first `Hello.hello` before asking the second for its `id`. – Veedrac Sep 16 '14 at 06:21
8

This is a "simple" consequence of how the memory allocator works. It is very similar to the case:

>>> id([]) == id([])
True

Basically python doesn't guarantee that ID's don't get reused -- it only guarantees that the id is unique as long as the object is alive. In this case, the first object being passed to id is dead after the call to id and (C)python re-uses that id when creating the second object.

Never rely on this behavior as it is allowed by the language reference, but certainly not required.

mgilson
  • 300,191
  • 65
  • 633
  • 696