Python 'is'运算符对非缓存整数的行为异常
在使用Python解释器时,我偶然发现了关于Python 'is'运算符对非缓存整数的行为异常,python,python-3.x,integer,identity,python-internals,Python,Python 3.x,Integer,Identity,Python Internals,在使用Python解释器时,我偶然发现了关于is操作符的一个冲突案例: 如果在函数中进行求值,则返回True,如果在函数外部进行求值,则返回False >>> def func(): ... a = 1000 ... b = 1000 ... return a is b ... >>> a = 1000 >>> b = 1000 >>> a is b, func() (False, True) 由于
is
操作符的一个冲突案例:
如果在函数中进行求值,则返回True
,如果在函数外部进行求值,则返回False
>>> def func():
... a = 1000
... b = 1000
... return a is b
...
>>> a = 1000
>>> b = 1000
>>> a is b, func()
(False, True)
由于is
运算符为所涉及的对象计算id()
,这意味着a
和b
在函数func
内部声明时指向相同的int
实例,但相反,在函数func
外部声明时指向不同的对象
为什么会这样
注意:我知道标识(
is
)和相等(=
)操作之间的区别,如中所述。此外,我还了解python正在对[-5256]
范围内的整数执行的缓存,如中所述。
这里的情况并非如此,因为数字超出了该范围,我确实想评估身份,而不是平等。 tl;博士: 作为缔约国: 块是作为一个单元执行的一段Python程序文本。 以下是模块、函数体和类定义。 以交互方式键入的每个命令都是一个块。 这就是为什么在函数的情况下,有一个单个代码块,其中包含一个单个数值文本对象
1000
,因此id(a)==id(b)
将产生True
在第二种情况下,您有两个不同的代码对象,每个对象都有各自不同的文本对象1000
soid(a)!=id(b)
请注意,这种行为不会仅在int
文本中出现,您将在float
文本中得到类似的结果(请参阅)
当然,比较对象(除了显式的is None
测试)应始终使用相等运算符==
而不是is
这里所述的一切都适用于最流行的Python实现CPython。其他实现可能有所不同,因此在使用它们时不应进行任何假设
详细回答: 为了获得更清晰的视图并进一步验证这种看似奇怪的行为,我们可以使用模块直接查看这些情况下的对象 对于函数
func
:
与所有其他属性一样,函数对象还有一个\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
属性,允许您查看该函数的编译字节码。使用,我们可以获得给定函数的代码对象中所有存储属性的漂亮视图:
>>> print(dis.code_info(func))
Name: func
Filename: <stdin>
Argument count: 0
Kw-only arguments: 0
Number of locals: 2
Stack size: 2
Flags: OPTIMIZED, NEWLOCALS, NOFREE
Constants:
0: None
1: 1000
Variable names:
0: a
1: b
当然,这将计算为True
,因为我们指的是同一个对象
对于每个交互命令:
如前所述,每个交互命令都被解释为单个代码块:独立地解析、编译和计算
我们可以通过内置程序获取每个命令的代码对象:
>>> com1 = compile("a=1000", filename="", mode="single")
>>> com2 = compile("b=1000", filename="", mode="single")
对于每个赋值语句,我们将得到一个类似的代码对象,如下所示:
>>> print(dis.code_info(com1))
Name: <module>
Filename:
Argument count: 0
Kw-only arguments: 0
Number of locals: 0
Stack size: 1
Flags: NOFREE
Constants:
0: 1000
1: None
Names:
0: a
这与我们实际得到的一致
不同的代码对象,不同的内容
注意:我对源代码中到底是如何发生这种情况有些好奇,在深入研究之后,我相信我终于找到了它 在编译阶段,属性由dictionary对象表示。在中,我们实际上可以看到初始化:
/* snippet for brevity */
u->u_lineno = 0;
u->u_col_offset = 0;
u->u_lineno_set = 0;
u->u_consts = PyDict_New();
/* snippet for brevity */
在编译过程中,将检查已有的常量。请参阅了解更多关于此的信息
注意事项:
- 链式语句将评估为
现在应该更清楚为什么下面的计算结果为True
:True
在这种情况下,通过将两个赋值命令链接在一起,我们告诉解释器将它们编译在一起。与函数对象的情况一样,仅为文字>>> a = 1000; b = 1000; >>> a is b
创建一个对象,从而在计算时产生1000
值True
- 在模块级执行会再次产生
: 如前所述,参考手册规定: 。。。以下是模块:模块 因此,同样的前提也适用:我们将有一个单一的代码对象(对于模块),因此,每个不同的文本存储单个值True
- 相同的不适用于可变的对象:
a=b=[]
),否则对象的标识将永远不会相等,例如:
a = []; b = []
a is b # always evaluates to False
同样,在中,这是规定的:
a=1后;b=1,a和b可能引用值为1的同一对象,也可能不引用,具体取决于实现,但在c=[]之后;d=[]、c和d保证引用两个不同的、唯一的、新创建的空列表
在交互提示下,条目是一次处理一个完整语句的条目。编译器本身(in)跟踪名为的字典中的常量,该字典将常量对象映射到其索引 在函数中,您可以看到,在添加新常量(并增加索引)之前,将检查dict以查看常量对象和索引是否已经存在。如果是这样,它们将被重用 简而言之,这意味着一条语句中的重复常量(例如在函数定义中)被折叠成一个单例。相反,您的
a=1000
和b=1000
是两个独立的语句,因此不会发生折叠
FWIW,这是
>>> a = 1000; b = 1000;
>>> a is b
a = []; b = []
a is b # always evaluates to False