专栏首页海纳周报详解Python的is操作符

详解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 的用法限制在本文的第一个例子中。这样相对会安全一些。

本文分享自微信公众号 - HinusWeekly(gh_4b8b4eda4e40),作者:海纳

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2017-12-26

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java的分代式GC

    要说理解JVM的垃圾回收,什么引用计数,Copy GC,mark & compaction好像都不是必须要掌握的东西。真要说对普通的Java程序员比较重要的东西...

    海纳
  • Java的字符串常量相关的一个问题

    大家过年好!春节假期休了一个长假,今天刚回来。在知乎上遇到了一个很好的问题,忍不住回答了一下。原文转载过来了。 以下代码的运行结果,如何解释? String h...

    海纳
  • 【第四期】GC专题

    我在某个技术群里发现很多人对GC的问题是最多的。确实,由于Java的GC经常会刷存在感(例如占用大量的CPU时间,full gc时直接失去响应),GC的问题就成...

    海纳
  • 统一异常处理这样剖析,安否?

    「欲渡黄河冰塞川,将登太行雪满天」,无论生活还是计算机世界难免发生异常,上一篇文章RESTful API 返回统一JSON数据格式 说明了统一返回的处理,这是请...

    用户4172423
  • 理解卷积神经网络中的输入与输出形状 | 视觉入门

    即使我们从理论上理解了卷积神经网络,在实际进行将数据拟合到网络时,很多人仍然对其网络的输入和输出形状(shape)感到困惑。本文章将帮助你理解卷积神经网络的输入...

    磐创AI
  • Springboot 2.0+FastDFS开发配置

    因为我们项目用的是Springboot 2.0以上的,所以跟Springboot 1.x的会有一些不同。

    算法之名
  • vue原来可以这样上手

           今儿与一群友讨论vue相关问题让我思量极深,1.我们是否在争对性解决问题或者说是帮助别人;2.我们是否在炫耀自己的技能。以下是被戏剧化的对白: ...

    sam dragon
  • Tungsten Fabric支持API一览

    通过在Tungsten Fabric外部虚拟IP地址的端口8082上访问的REST API,可以获得Tungsten Fabric群集的所有配置。 用户可以使用...

    Tungsten Fabric
  • 基于binlog的离线分析平台的一些初步实践

    http://quarterback.cn/%e9%80%9a%e8%bf%87kafka-nifi%e5%bf%ab%e9%80%9f%e6%9e%84%e5...

    二狗不要跑
  • 自定义字体

    本文作者:IMWeb 结一 原文出处:IMWeb社区 未经同意,禁止转载 概述 一般来说,网页上的字体使用的都是我们电脑里面的字体,比如我们常说的微软...

    IMWeb前端团队

扫码关注云+社区

领取腾讯云代金券