举例讲解Python中is和id的用法


Posted in Python onApril 03, 2015

(ob1 is ob2) 等价于 (id(ob1) == id(ob2))

首先id函数可以获得对象的内存地址,如果两个对象的内存地址是一样的,那么这两个对象肯定是一个对象。和is是等价的。Python源代码为证。
 

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;

但是请看下边代码的这种情况怎么会出现呢?

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

两个对象用is判断是False,用id判断却是True,这与我们已知的事实不符啊,这种现象该如何解释呢?遇到这种情况最好的解决方法就是调用dis模块去看下两个比较语句到底做了什么。

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)

真实情况是当执行.操作符的时候,实际是生成了一个proxy对象,foo.bar is Foo.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。

下面内容由邮件Leo Jay大牛提供,他解释的更加通透。

用id(expression a) == id(expression b)来判断两个表达式的结果是不是同一个对象的想法是有问题的。

foo.bar 这种形式叫 attribute reference [1],它是表达式的一种。foo是一个instance object,bar是一个方法,这个时候表达式foo.bar返回的结果叫method object [2]。根据文档:

    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 to) the instance object and the function object just found
    together in an abstract object: this is the method object.

foo.bar本身并不是简单的名字,而是表达式的计算结果,是一个 method object,在id(foo.bar)这样的表达式里,method object只是一个临时的中间变量而已,对临时的中间变量做id是没有意义的。
一个更明显的例子是,
 

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

输出的结果也是True

看 id 的文档[3]:

    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.

只有你能保证对象不会被销毁的前提下,你才能用 id 来比较两个对象。所以,如果你非要比的话,得这样写:
 

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

即把两个表达式的结果绑定到名字上,再来比是不是同一个对象,你才能得到正确的结果。

is表达式 [4] 也是一样的,你现在得到了正确的结果,完全是因为 CPython 现在的实现细节决定的。现在的is的实现,是左右两边的对象都计算出来,然后再比较这两个对象的地址是否一样。万一哪天改成了,先算左边,保存地址,把左边释放掉,再算右边,再比较的话,你的is的结果可能就错了。官方文档里也提到了这个问题 [5]。我认为正确的方法也是像id那样,先把左右两边都计算下来,并显式绑定到各自的名字上,然后再用is判断。

Python 相关文章推荐
基于python的汉字转GBK码实现代码
Feb 19 Python
Python实现把json格式转换成文本或sql文件
Jul 10 Python
浅谈Python 集合(set)类型的操作——并交差
Jun 30 Python
django模型层(model)进行建表、查询与删除的基础教程
Nov 21 Python
DataFrame中的object转换成float的方法
Apr 10 Python
python数字图像处理之高级形态学处理
Apr 27 Python
python检测主机的连通性并记录到文件的实例
Jun 21 Python
详解Python的数据库操作(pymysql)
Apr 04 Python
Tornado实现多进程/多线程的HTTP服务详解
Jul 25 Python
浅析Django中关于session的使用
Dec 30 Python
什么是python的id函数
Jun 11 Python
Python读取二进制文件代码方法解析
Jun 22 Python
详解Python2.x中对Unicode编码的使用
Apr 03 #Python
对于Python中线程问题的简单讲解
Apr 03 #Python
python BeautifulSoup设置页面编码的方法
Apr 03 #Python
用Python编写一个简单的FUSE文件系统的教程
Apr 02 #Python
用Python中的__slots__缓存资源以节省内存开销的方法
Apr 02 #Python
用Python的线程来解决生产者消费问题的示例
Apr 02 #Python
用实例分析Python中method的参数传递过程
Apr 02 #Python
You might like
php与paypal整合方法
2010/11/28 PHP
JpGraph php柱状图使用介绍
2011/08/23 PHP
PHP json_decode函数详细解析
2014/02/17 PHP
php输入流php://input使用浅析
2014/09/02 PHP
PHP中使用CURL获取页面title例子
2015/01/07 PHP
实例分析基于PHP微信网页获取用户信息
2017/11/24 PHP
防止网站内容被拷贝的一些方法与优缺点好处与坏处分析
2007/11/30 Javascript
用js+iframe形成页面的一种遮罩效果的具体实现
2013/12/31 Javascript
JS图像无缝滚动脚本非常好用
2014/02/10 Javascript
JavaScript中双叹号(!!)作用示例介绍
2014/04/10 Javascript
jquery实现叠层3D文字特效代码分享
2015/08/21 Javascript
Java遍历集合方法分析(实现原理、算法性能、适用场合)
2016/04/25 Javascript
使用BootStrap进行轮播图的制作
2017/01/06 Javascript
详解vue-cli 脚手架项目-package.json
2017/07/04 Javascript
nodejs开发微信小程序实现密码加密
2017/07/11 NodeJs
JavaScript惰性求值的一种实现方法示例
2019/01/11 Javascript
浅谈vue中组件绑定事件时是否加.native
2019/11/09 Javascript
浅谈vuex为什么不建议在action中修改state
2020/02/02 Javascript
javascript如何使用函数random来实现课堂随机点名方法详解
2020/07/28 Javascript
Python脚本文件打包成可执行文件的方法
2015/06/02 Python
python3+PyQt5使用数据库窗口视图
2018/04/24 Python
解决PyCharm同目录下导入模块会报错的问题
2018/10/13 Python
详解Python sys.argv使用方法
2019/05/10 Python
Python-Seaborn热图绘制的实现方法
2019/07/15 Python
python代码打印100-999之间的回文数示例
2019/11/24 Python
利用Pytorch实现简单的线性回归算法
2020/01/15 Python
基于TensorFlow中自定义梯度的2种方式
2020/02/04 Python
橄榄树药房:OLIVEDA
2019/09/01 全球购物
中班中秋节活动反思
2014/02/18 职场文书
2014信息公开实施方案
2014/02/22 职场文书
秋天的怀念教学反思
2014/04/28 职场文书
初三新学期计划书
2014/05/03 职场文书
文明工地标语
2014/06/16 职场文书
2015年春训学习心得体会范文
2015/03/09 职场文书
学困生帮扶工作总结
2015/08/13 职场文书
《7的乘法口诀》教学反思
2016/02/18 职场文书