要理解is操作符的话,大家可以与Java中的 == 操作符相互印证一下,加深一下对引用和对象的理解

is操作符与 == 的区别

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中把-127到128这些小整数都缓存了一份。这和Java的Integer类是一样的。所以,对于-127到128之间的整数,整个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

然后再查看一下这个文件:

import dis, marshal, struct, sys, time, types   def show_file(fname):      f = open(fname, "rb")      magic = f.read(4)      moddate = f.read(4)  #    modtime = time.asctime(time.localtime(struct.unpack('L', moddate)[0]))   print "magic %s" % (magic.encode('hex'))   print "moddate %s" % (moddate.encode('hex'))      code = marshal.load(f)      show_code(code)   def show_code(code, indent=''):      old_indent = indent   print "%s" % indent      indent += ' '  print "%s %d " % (indent, code.co_argcount)   print "%s %d" % (indent, code.co_nlocals)   print "%s %d" % (indent, code.co_stacksize)   print "%s %04x" % (indent, code.co_flags)      show_hex("code", code.co_code, indent=indent)   print "%s" % indent      dis.disassemble(code)   print "%s" % indent    print "%s %r" % (indent, code.co_names) print "%s %r" % (indent, code.co_varnames) print "%s %r" % (indent, code.co_freevars) print "%s %r" % (indent, code.co_cellvars) print "%s %r" % (indent, code.co_filename) print "%s %r" % (indent, code.co_name) print "%s %d" % (indent, code.co_firstlineno)  print "%s" % indent for const in code.co_consts: if type(const) == types.CodeType:            show_code(const, indent+' ') else: print " %s%r" % (indent, const) print "%s" % indent     show_hex("lnotab", code.co_lnotab, indent=indent) print "%s" % old_indent def show_hex(label, h, indent):    h = h.encode('hex') if len(h) < 60: print "%s<%s> %s" % (indent, label, h,label) else: print "%s<%s>" % (indent, label) for i in range(0, len(h), 60): print "%s %s" % (indent, h[i:i+60]) print "%s" % (indent, label) show_file(sys.argv[1])

得到这样的输出:


    0  0 2 0040
      6400005a00006400005a01006500006501006b080047486400005a000064
      01008400005a02006502008300006500006b0800474864020053
   
   
  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 ()
             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        
   
    ('a', 'b', 'f') () () () 'testis.py' '' 1
      10.0
      
          0  0 1 0043 64010053
         
  7           0 LOAD_CONST               1 (10.0)
              3 RETURN_VALUE        
          () () () () 'testis.py' 'f' 6
            None
            10.0
          0001
      None
    060106010b0206010902

大家注意看,整个python文件其实就是一个大的对象。f 所对应的那个函数也是一个对象,这个code对象做为整体是大的对象的consts域里的一个const项。再注意,在大对象里,有10.0这样的一个const项,f 这个对象所对应的conts里,也有一个10.0这个浮点数。

当python在加载这个文件的时候,就会完成主里的10.0这个浮点数的加载,生成一个PyFloatObject。也就是说静态的pyc文件的常量表在被加载以后,就变成了内存中的常量表。文件的表里的10.0就变成了内存中的一个PyFloatObject。所以,a, b两个变量都会引用这个PyFloatObject。

但是 f 里的那个10.0呢?它是要等到MAKE_FUNCTION被调用的时候才会真正地初始化。做为 f 方法的返回值,它必然与我们之前所说的主里的10.0不是同一个对象了。

本质上讲,这是Python的一个设计缺陷(例如Java以一个文件为编译单元,共享同一个常量池就会减轻这个问题。但如果跨文件使用 == 操作符,也会出现同样的问题。仍然没有解决这个问题。实际上,我自己也不知道该怎么解决这个问题。)我们应该尽量避免 is 的这种用法。始终把 is 的用法限制在本文的第一个例子中。这样相对会安全一些。