举例讲解Python中is和id的用法
(ob1isob2)等价于(id(ob1)==id(ob2))
首先id函数可以获得对象的内存地址,如果两个对象的内存地址是一样的,那么这两个对象肯定是一个对象。和is是等价的。Python源代码为证。
staticPyObject* cmp_outcome(intop,registerPyObject*v,registerPyObject*w) { intres=0; switch(op){ casePyCmp_IS: res=(v==w); break; casePyCmp_IS_NOT: res=(v!=w); break;
但是请看下边代码的这种情况怎么会出现呢?
In[1]:defbar(self,x): ...:returnself.x+y ...: In[2]:classFoo(object): ...:x=9 ...:def__init__(self,x): ...:self.x=x ...:bar=bar ...: In[3]:foo=Foo(5) In[4]:foo.barisFoo.bar Out[4]:False In[5]:id(foo.bar)==id(Foo.bar) Out[5]:True
两个对象用is判断是False,用id判断却是True,这与我们已知的事实不符啊,这种现象该如何解释呢?遇到这种情况最好的解决方法就是调用dis模块去看下两个比较语句到底做了什么。
In[7]:dis.dis("id(foo.bar)==id(Foo.bar)") 0BUILD_MAP10340 3BUILD_TUPLE28527 6<46> 7DELETE_GLOBAL29281(29281) 10STORE_SLICE+1 11SLICE+2 12DELETE_SUBSCR 13DELETE_SUBSCR 14SLICE+2 15BUILD_MAP10340 18PRINT_EXPR 19JUMP_IF_FALSE_OR_POP11887 22DELETE_GLOBAL29281(29281) 25STORE_SLICE+1 In[8]:dis.dis("foo.barisFoo.bar") 0BUILD_TUPLE28527 3<46> 4DELETE_GLOBAL29281(29281) 7SLICE+2 8BUILD_MAP8307 11PRINT_EXPR 12JUMP_IF_FALSE_OR_POP11887 15DELETE_GLOBAL29281(29281)
真实情况是当执行.操作符的时候,实际是生成了一个proxy对象,foo.barisFoo.bar的时候,两个对象顺序生成,放在栈里相比较,由于地址不同肯定是False,但是id(foo.bar)==id(Foo.bar)的时候就不同了,首先生成foo.bar,然后计算foo.bar的地址,计算完之后foo.bar的地址之后,就没有任何对象指向foo.bar了,所以foo.bar对象就会被释放。然后生成Foo.bar对象,由于foo.bar和Foo.bar所占用的内存大小是一样的,所以又恰好重用了原先foo.bar的内存地址,所以id(foo.bar)==id(Foo.bar)的结果是True。
下面内容由邮件LeoJay大牛提供,他解释的更加通透。
用id(expressiona)==id(expressionb)来判断两个表达式的结果是不是同一个对象的想法是有问题的。
foo.bar这种形式叫attributereference[1],它是表达式的一种。foo是一个instanceobject,bar是一个方法,这个时候表达式foo.bar返回的结果叫methodobject[2]。根据文档:
Whenaninstanceattributeisreferencedthatisn'tadataattribute,
itsclassissearched.Ifthenamedenotesavalidclassattribute
thatisafunctionobject,amethodobjectiscreatedbypacking
(pointersto)theinstanceobjectandthefunctionobjectjustfound
togetherinanabstractobject:thisisthemethodobject.
foo.bar本身并不是简单的名字,而是表达式的计算结果,是一个methodobject,在id(foo.bar)这样的表达式里,methodobject只是一个临时的中间变量而已,对临时的中间变量做id是没有意义的。
一个更明显的例子是,
printid(foo.bar)==id(foo.__init__)
输出的结果也是True
看id的文档[3]:
Returnthe“identity”ofanobject.Thisisaninteger(orlong
integer)whichisguaranteedtobeuniqueandconstantforthisobject
duringitslifetime.Twoobjectswithnon-overlappinglifetimesmay
havethesameid()value.
CPythonimplementationdetail:Thisistheaddressoftheobjectinmemory.
只有你能保证对象不会被销毁的前提下,你才能用id来比较两个对象。所以,如果你非要比的话,得这样写:
fb=foo.bar Fb=Foo.bar printid(fb)==id(Fb)
即把两个表达式的结果绑定到名字上,再来比是不是同一个对象,你才能得到正确的结果。
is表达式[4]也是一样的,你现在得到了正确的结果,完全是因为CPython现在的实现细节决定的。现在的is的实现,是左右两边的对象都计算出来,然后再比较这两个对象的地址是否一样。万一哪天改成了,先算左边,保存地址,把左边释放掉,再算右边,再比较的话,你的is的结果可能就错了。官方文档里也提到了这个问题[5]。我认为正确的方法也是像id那样,先把左右两边都计算下来,并显式绑定到各自的名字上,然后再用is判断。