Usage analysis of is and id in Python

  • 2020-04-02 14:31:35
  • OfStack

This article illustrates the use of is and id in Python. Share with you for your reference. Specific analysis is as follows:

Ob1 is ob2 is the same thing as id(ob1) == id(ob2).

First, the id function can get the memory address of the object. If the memory address of two objects is the same, then the two objects must be one object. It's the same thing as is. The Python source code is a proof.

static PyObject *
 cmp_outcome(int op, register PyObject *v, register PyObject *w)
{
 int res = 0;
 switch (op) {
 case PyCmp_IS:
  res = (v == w);
 break;
 case PyCmp_IS_NOT:
res = (v != w);
 break;

But look at the code below and how does this happen?

In [1]: def bar(self, x):
...:     return self.x + y
...:
 
In [2]: class Foo(object):
...:     x = 9
...:     def __init__(self ,x):
...:         self.x = x
...:     bar = bar
...:    
 
In [3]: foo = Foo(5)
 
In [4]: foo.bar is Foo.bar
Out[4]: False
 
In [5]: id(foo.bar) == id(Foo.bar)
Out[5]: True

Two objects are judged to be False by is and True by id, which is inconsistent with the fact that we already know. How to explain this phenomenon? The best solution to this situation is to call the dis module to see what the two comparison statements do.

In [7]: dis.dis("id(foo.bar) == id(Foo.bar)")
          0 BUILD_MAP       10340
          3 BUILD_TUPLE     28527
          6 <46>          
          7 DELETE_GLOBAL   29281 (29281)
         10 STORE_SLICE+1
         11 SLICE+2      
         12 DELETE_SUBSCR 
         13 DELETE_SUBSCR 
         14 SLICE+2      
         15 BUILD_MAP       10340
         18 PRINT_EXPR    
         19 JUMP_IF_FALSE_OR_POP 11887
         22 DELETE_GLOBAL   29281 (29281)
         25 STORE_SLICE+1
 
In [8]: dis.dis("foo.bar is Foo.bar")
          0 BUILD_TUPLE     28527
          3 <46>          
          4 DELETE_GLOBAL   29281 (29281)
          7 SLICE+2      
          8 BUILD_MAP        8307
         11 PRINT_EXPR    
         12 JUMP_IF_FALSE_OR_POP 11887
         15 DELETE_GLOBAL   29281 (29281)

The truth is when performing. Operator, is actually generates a proxy object, foo bar is foo bar, two objects sequence generation, in the stack, due to different address must be False, but id (foo, bar) = = id (foo, bar) is different, the first generation foo bar, and then calculate the foo bar address, after calculating foo address bar, there is no point any object foo. Bar, So the foo.bar object will be released. Then the foo.bar object is generated. Since the memory size of foo.bar and foo.bar are the same, the memory address of the original foo.bar is exactly reused, so the result of id(foo.bar) == id(foo.bar) is True.

The following content is provided by Leo Jay Daniel in the email, and he explains it more clearly.

The idea of using id(expression a) == id(expression b) to determine whether the result of two expressions is the same object is problematic.

The form foo.bar is called attribute reference [1], and it is one of the expressions. Foo is an instance object,bar is a method, and the result returned by the expression foo.bar is called method object. According to the document:

When an instance attribute is referenced that isn't a data attribute,
Its class is searched. If the name denotes a valid class attribute
That is a function object, a method object is created by packing
(Pointers) the instance object and the function object just found
Together in an abstract object: this is the method object.

Foo.bar itself is not a simple name, but the calculation result of the expression, which is a method object. In an expression like id(foo.bar), method object is just a temporary intermediate variable.

A more obvious example is,

print id(foo.bar) == id(foo.__init__)

The output is also True

See the id document:

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.
CPython implementation detail: This is the address of the object in memory.

You can only use id to compare two objects if you can guarantee that the object will not be destroyed. So, if you must, write:

fb = foo.bar 
Fb = Foo.bar
print id(fb) == id(Fb)

That is, bind the result of two expressions to the name, and then compare the same object, you can get the correct result.

The is expression is the same, you now get the correct result, because of the implementation details of CPython. Now the implementation of is is to calculate the left and right objects, and then compare the two objects with the same address. If you change it one day, you take the left side, you save the address, you release the left side, you take the right side, and you compare it, you might get the is wrong. Official documents also mention the problem. I think the right way is to do the same thing as id, which is to calculate the left and right sides and explicitly bind them to their names, and then use is.

I hope this article has helped you with your Python programming.


Related articles: