gpt4 book ai didi

python - “is”运算符对非缓存整数的行为异常

转载 作者:太空宇宙 更新时间:2023-11-03 12:00:12 25 4
gpt4 key购买 nike

在使用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运算符计算所涉及对象的 id(),这意味着 ab在函数 int内部声明时指向同一个 func实例,但在函数 is外部声明时指向不同的对象。
为什么会这样?
注意:我知道在 Understanding Python's "is" operator中描述的identity( ==)和equality( [-5, 256])操作之间的区别此外,我还知道python正在为 范围内的整数执行缓存,如 "is" operator behaves unexpectedly with integers中所述。
这里不是这样的,因为数字在这个范围之外,我确实想评估身份,而不是平等。

最佳答案

TL;博士:
正如reference manual所述:
块是作为一个单元执行的一段python程序文本。
以下是块:模块、函数体和类定义。
交互输入的每个命令都是一个块。
这就是为什么在函数的情况下,只有一个代码块,其中包含数字文本的单个对象
1000,因此id(a) == id(b)将产生True
在第二种情况下,您有两个不同的代码对象,每个对象都有自己的文本1000soid(a) != id(b)对象。
请注意,此行为并不仅与int文本一起显示,您将获得类似的结果,例如,float文本(请参见here)。
当然,比较对象(显式is None测试除外)应该始终使用相等运算符==而不是is
这里所述的一切都适用于最流行的python实现cpython。其他实现可能有所不同,因此在使用它们时不应进行任何假设。
更长的答案:
为了获得更清晰的视图并进一步验证这种看似奇怪的行为,我们可以使用code模块直接查看这些情况下的dis对象。
对于函数func
除了所有其他属性之外,函数对象还有一个__code__属性,允许您查看该函数的编译字节码。使用dis.code_info可以很好地查看给定函数的代码对象中存储的所有属性:

>>> 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

我们只对函数 Constantsfunc项感兴趣。在它中,我们可以看到有两个值, None(始终存在)和 1000我们只有一个int实例表示常量 1000。这是调用函数时要分配给 ab的值。
通过 func.__code__.co_consts[1]访问此值很容易,因此,在函数中查看 a is b评估的另一种方法如下:
>>> id(func.__code__.co_consts[1]) == id(func.__code__.co_consts[1]) 

当然,因为我们指的是同一个对象,所以它的计算结果会小于cc>。
对于每个交互命令:
如前所述,每个交互命令被解释为一个单独的代码块:独立地解析、编译和计算。
我们可以通过内置的 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

相同的 compile命令看起来相同,但有一个根本的区别:每个代码对象 com2com1都有不同的int实例表示文本 com2。这就是为什么,在这种情况下,当我们通过 1000参数 a is b时,我们实际上得到:
>>> id(com1.co_consts[0]) == id(com2.co_consts[0])
False

这与我们实际得到的一致。
不同的代码对象,不同的内容。
注意:我有点好奇,这到底是怎么发生在源代码中,经过挖掘,我相信我终于找到了它。
在编译阶段, co_consts属性由dictionary对象表示。在 co_consts中,我们可以看到初始化:
/* 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 */

在编译过程中,检查已经存在的常量。请参见 @Raymond Hettinger's answer below了解更多信息。
注意事项:
链式语句将计算为 compile.c
现在应该更清楚的是,为什么下面的计算结果是 True
>>> a = 1000; b = 1000;
>>> a is b

在这种情况下,通过将两个赋值命令链接在一起,我们告诉解释器将它们编译在一起。与函数对象的情况一样,只有一个文本 True的对象将被创建,从而在计算时产生 1000值。
在模块级执行会再次产生 True
如前所述,参考手册规定:
... 以下是块:一个模块。。。
因此,同样的前提也适用:我们将有一个单独的代码对象(对于模块),因此,每个不同的文本存储一个值。
这同样不适用于可变对象:
也就是说,除非我们显式初始化为同一个可变对象(例如a=b=[]),否则对象的标识永远不会相等,例如:
a = []; b = []
a is b # always returns false

同样,在 the documentation中,这是指定的:
在a=1;b=1之后,a和b可能引用值为1的同一个对象,也可能不引用值为1的同一个对象,具体取决于实现,但是在c=[];d=[]之后,c和d保证引用两个不同的、唯一的、新创建的空列表。

关于python - “is”运算符对非缓存整数的行为异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51259003/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com