详解Python的is操作符
is 操作符是Python语言的一个内建的操作符。它的作用在于比较两个变量是否指向了同一个对象。
与 == 的区别
class A():
def __init__(self, v):
self.value = v
def __eq__(self, t):
return self.value == t.value
a = A(3)
b = A(3)
print a == b
print a is b
这个结果是True,False。因为我们重写了__eq__方法就使得a, b在比较的时候,只比较它们的value即可。只要它们的value相等,那么a, b就是相等的。
而 is 操作符是判断两个变量是否引用了同一个对象。
同一个对象?
is 的用法说起来其实挺简单的,但是真正用起来,它的难点恰恰就在于判断哪些对象是同一个对象。
看下面的几个测试,先不看结果,自己能答对多少?
a = 10
b = 10
print a is b
a = 10.0
b = 10.0
print a is b
a = 10
def f():
return 10
print f() is a
a = 1000
def f():
return 1000
print f() is a
a = 10.0
def f():
return 10.0
print f() is a
嗯。这个结果是True, True, True, False, False。你答对了吗?
这个结果中牵扯到两个问题:第一,就是小整数的缓存,第二,就是pyc文件中CodeObject的组织问题。
Python中把-128到127这些小整数都缓存了一份。这和Java的Integer类是一样的。所以,对于-128到127之间的整数,整个Python虚拟机中就只有一个实例。不管你什么时候,什么场景下去使用 is 进行判断,都会是True,所以我们知道了这两个测试一定会是True:
a = 10
b = 10
print a is b
a = 10
def f():
return 10
print f() is a
接着,我们重点看下,这两个测试:
a = 10.0
b = 10.0
print a is b
a = 10.0
def f():
return 10.0
print f() is a
为什么一个是True,一个是False。要探究这个问题,就要从字节码的角度去分析了。我们先把这个文件编译一下:
python -m compileall testis.py
然后再使用这个工具查看一下字节码文件:https://github.com/hinus/railgun/blob/master/src/main/python/rgparser/show.py
得到这样的输出:
<code>
<argcount> 0 </argcount>
<nlocals> 0</nlocals>
<stacksize> 2</stacksize>
<flags> 0040</flags>
<code>
6400005a00006400005a01006500006501006b080047486400005a000064
01008400005a02006502008300006500006b0800474864020053 </code>
<dis>
1 0 LOAD_CONST 0 (10.0)
3 STORE_NAME 0 (a)
2 6 LOAD_CONST 0 (10.0)
9 STORE_NAME 1 (b)
3 12 LOAD_NAME 0 (a)
15 LOAD_NAME 1 (b)
18 COMPARE_OP 8 (is)
21 PRINT_ITEM
22 PRINT_NEWLINE
5 23 LOAD_CONST 0 (10.0)
26 STORE_NAME 0 (a)
6 29 LOAD_CONST 1 (<code object f>)
32 MAKE_FUNCTION 0
35 STORE_NAME 2 (f)
8 38 LOAD_NAME 2 (f)
41 CALL_FUNCTION 0
44 LOAD_NAME 0 (a)
47 COMPARE_OP 8 (is)
50 PRINT_ITEM
51 PRINT_NEWLINE
52 LOAD_CONST 2 (None)
55 RETURN_VALUE
</dis>
<names> ('a', 'b', 'f')</names>
<varnames> ()</varnames>
<freevars> ()</freevars>
<cellvars> ()</cellvars>
<filename> 'testis.py'</filename>
<name> '<module>'</name>
<firstlineno> 1</firstlineno>
<consts>
10.0 <code>
<argcount> 0 </argcount>
<nlocals> 0</nlocals>
<stacksize> 1</stacksize>
<flags> 0043</flags>
<code> 64010053</code>
<dis>
7 0 LOAD_CONST 1 (10.0)
3 RETURN_VALUE
</dis>
<names> ()</names>
<varnames> ()</varnames>
<freevars> ()</freevars>
<cellvars> ()</cellvars>
<filename> 'testis.py'</filename>
<name> 'f'</name>
<firstlineno> 6</firstlineno>
<consts>
None
10.0 </consts>
<lnotab> 0001</lnotab>
</code>
None </consts>
<lnotab> 060106010b0206010902</lnotab></code>
大家注意看,整个python文件其实就是一个大的<code>对象,f 所对应的那个函数也是一个<code>对象,这个code对象做为整体是大的<code>对象的consts域里的一个const项。再注意,在大<code>对象里,有10.0这样的一个const项,f 这个<code>对象所对应的conts里呢,也有一个10.0这个浮点数。
当python在加载这个文件的时候,就会完成主<code>里的10.0这个浮点数的加载,生成一个PyFloatObject。也就是说静态的pyc文件的常量表在被加载以后,就变成了内存中的常量表,文件的表里的10.0就变成了内存中的一个PyFloatObject。所以,a, b两个变量都会引用这个PyFloatObject。
但是 f 里的那个10.0呢?它是要等到MAKE_FUNCTION被调用的时候才会真正地初始化。做为 f 方法的返回值,它必然与我们之前所说的主<code>里的10.0不是同一个对象了。
本质上讲,这是Python的一个设计缺陷(例如Java以一个文件为编译单元,共享同一个常量池就会减轻这个问题。但如果跨文件使用 == 操作符,也会出现同样的问题。仍然没有解决这个问题。实际上,我自己也不知道该怎么解决这个问题。)我们应该尽量避免 is 的这种用法。始终把 is 的用法限制在本文的第一个例子中。这样相对会安全一些。
- python爬虫入门(四)利用多线程爬虫
- LOJ #115. 无源汇有上下界可行流
- 数据库改名系列(数据库名,逻辑名,物理文件名)
- BZOJ1468: Tree
- 洛谷P3806 【模板】点分治1
- 探索ASP.NET MVC5系列之~~~5.缓存篇(页面缓存+二级缓存)
- 洛谷P3383 【模板】线性筛素数(Miller_Rabin)
- BZOJ3667: Rabin-Miller算法
- Numpy 修炼之道 (2)—— N维数组 ndarray
- python爬虫入门(五)Selenium模拟用户操作
- python爬虫入门(六) Scrapy框架之原理介绍
- lambda表达式杂谈
- python爬虫入门(七)Scrapy框架之Spider类
- python爬虫入门(八)Scrapy框架之CrawlSpider类
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 用Redis构建缓存集群的最佳实践有哪些?
- Android实现IP地址输入框的方法示例代码
- Node.js 搭建 HTTPS 服务器
- Android布局之表格布局TableLayout详解
- 简单实现Android倒计时效果
- Android实现单页面浮层可拖动view的一种方法
- 排查 Node.js 服务内存泄漏,没想到竟是它?
- Android判断网络状态的代码
- Android开发实现布局中为控件添加选择器的方法
- Android控制文本输入框最多输入10个字符长度
- Elasticsearch 内部数据结构深度解读
- 关于 Elasticsearch 段合并,这一篇说透了!
- 解了这十道C语言题,你敢说你精通C语言?
- 微服务中使用Maven BOM来管理你的服务版本
- 设计模式之代理模式(文末赠书)