举例讲解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 SSH模块登录,远程机执行shell命令实例解析
Jan 12 Python
python Flask 装饰器顺序问题解决
Aug 08 Python
python 实现调用子文件下的模块方法
Dec 07 Python
python使用参数对嵌套字典进行取值的方法
Apr 26 Python
利用python list完成最简单的DB连接池方法
Aug 09 Python
django drf框架中的user验证以及JWT拓展的介绍
Aug 12 Python
python同步两个文件夹下的内容
Aug 29 Python
Python根据服务获取端口号的方法
Sep 25 Python
Flask框架路由和视图用法实例分析
Nov 07 Python
Django在Model保存前记录日志实例
May 14 Python
Python实现画图软件功能方法详解
Jul 28 Python
Python标准库之typing的用法(类型标注)
Jun 02 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
Syphon 虹吸式咖啡壶冲煮–拨动法
2021/03/03 冲泡冲煮
PHP获取文件夹内文件数的方法
2015/03/12 PHP
详解PHP的Yii框架中扩展的安装与使用
2016/04/01 PHP
JavaScript isArray()函数判断对象类型的种种方法
2010/10/11 Javascript
JS操作Cookie写入和读取实例代码
2013/10/20 Javascript
jQuery实现拖拽效果插件的方法
2015/03/23 Javascript
js实现缓冲运动效果的方法
2015/04/10 Javascript
Javascript中For In语句用法实例
2015/05/14 Javascript
浅谈javascript中return语句
2015/07/15 Javascript
javascript父子页面通讯实例详解
2015/07/17 Javascript
详解参数传递四种形式
2015/07/21 Javascript
jQuery防止重复绑定事件的解决方法
2016/05/14 Javascript
jquery attr()设置和获取属性值实例教程
2016/09/25 Javascript
javascript使用闭包模拟对象的私有属性和方法
2016/10/05 Javascript
jQuery动态生成表格及右键菜单功能示例
2017/01/13 Javascript
vuejs2.0运用原生js实现简单的拖拽元素功能示例
2017/02/24 Javascript
jQuery实现的页面遮罩层功能示例【测试可用】
2017/10/14 jQuery
ReactNative 之FlatList使用及踩坑封装总结
2017/11/29 Javascript
在ES5与ES6环境下处理函数默认参数的实现方法
2018/05/13 Javascript
cnpm加速Angular项目创建的方法
2018/09/07 Javascript
vue-router之nuxt动态路由设置的两种方法小结
2018/09/26 Javascript
Three.js实现3D机房效果
2018/12/30 Javascript
基于Vue插入视频的2种方法小结
2019/04/02 Javascript
Angular8路由守卫原理和使用方法
2019/08/29 Javascript
vue Cli 环境删除与重装教程 - 版本文档
2020/09/11 Javascript
openlayers实现图标拖动获取坐标
2020/09/25 Javascript
Python标准库笔记struct模块的使用
2018/02/22 Python
对python:循环定义多个变量的实例详解
2019/01/20 Python
如何将你的应用迁移到Python3的三个步骤
2019/12/22 Python
北京SQL新华信咨询
2016/09/30 面试题
淘宝活动策划方案
2014/02/06 职场文书
工作说明书范文
2014/05/07 职场文书
读《推着妈妈去旅行》有感1500字
2019/10/15 职场文书
教你如何用python开发一款数字推盘小游戏
2021/04/14 Python
javascript的setTimeout()使用方法总结
2021/11/20 Javascript
golang生成并解析JSON
2022/04/14 Golang