gpt4 book ai didi

python - `is` 运算符在处理非缓存整数时出现意外行为

转载 作者:IT老高 更新时间:2023-10-28 21:34:47 26 4
gpt4 key购买 nike

在玩 Python 解释器时,我偶然发现了这个关于 is 的冲突案例。运算符(operator):

如果评估发生在函数中,则返回 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 中所述.此外,我还知道 python 正在为范围内的整数执行缓存 [-5, 256]"is" operator behaves unexpectedly with integers 中所述.

不是这里的情况 因为数字超出了该范围和 我愿意 想评估身份和 不是 平等。

最佳答案

tl;博士:
reference manual状态:

A block is a piece of Python program text that is executed as a unit.The following are blocks: a module, a function body, and a class definition.Each command typed interactively is a block.


这就是为什么,在函数的情况下,你有一个 单例包含 的代码块单例数字文字的对象 1000 , 所以 id(a) == id(b)将产生 True .
在第二种情况下,您有 两个不同的代码对象 每个都有自己不同的文字对象 1000所以 id(a) != id(b) .
请注意,此行为不会通过 int 表现出来。仅文字,您将获得类似的结果,例如 float文字(见 here )。
当然,比较对象(显式 is None 测试除外)应该始终使用相等运算符 == 来完成。而不是 is .
这里所说的一切都适用于最流行的 Python 实现,CPython。其他实现可能会有所不同,因此在使用它们时不应做出任何假设。

更长的答案:
为了获得更清晰的 View 并另外验证这种看似奇怪的行为,我们可以直接查看 code 使用 dis 为每种情况设置对象模块。
对于函数 func :
除了所有其他属性,函数对象还有一个 __code__允许您查看该函数的已编译字节码的属性。使用 dis.code_info 对于给定函数,我们可以获得代码对象中所有存储属性的漂亮 View :
>>> 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
我们只对 Constants 感兴趣函数条目 func .在其中,我们可以看到我们有两个值, None (始终存在)和 1000 .我们只有一个 单例表示常量 1000 的 int 实例.这是 a 的值和 b将在调用函数时分配给。
通过 func.__code__.co_consts[1] 可以轻松访问此值因此,另一种查看我们的方式 a is b函数中的评估如下所示:
>>> id(func.__code__.co_consts[1]) == id(func.__code__.co_consts[1]) 
当然,这将评估为 True因为我们指的是同一个对象。
对于每个交互式命令:
如前所述,每个交互式命令都被解释为单个代码块:独立解析、编译和评估。
我们可以通过 compile 获取每个命令的代码对象内置:
>>> 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
com2 的相同命令看起来相同但有根本区别:每个代码对象 com1com2有代表文字 1000 的不同 int 实例.这就是为什么,在这种情况下,当我们做 a is b通过 co_consts论证,我们实际上得到:
>>> id(com1.co_consts[0]) == id(com2.co_consts[0])
False
这与我们实际得到的一致。
不同的代码对象,不同的内容。

注:我有点好奇这在源代码中究竟是如何发生的,在深入研究之后我相信我终于找到了它。
在编译阶段 co_consts 属性由字典对象表示。在 compile.c 我们实际上可以看到初始化:
/* 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关于这一点的更多信息。

注意事项:
  • 链式语句将评估为 True 的身份检查
    现在应该更清楚为什么下面的计算结果为 True :
     >>> a = 1000; b = 1000;
    >>> a is b
    在这种情况下,通过将两个赋值命令链接在一起,我们告诉解释器编译这些 一起 .与函数对象的情况一样,文字 1000 只有一个对象。将创建导致 True评估时的值(value)。
  • 在模块级别执行产生 True再次:
    如前所述,引用手册指出:

    ... The following are blocks: a module ...


    所以同样的前提适用:我们将有一个单一的代码对象(用于模块),因此,每个不同的文字都会存储单个值。
  • 没有 申请可变 对象:

  • 这意味着除非我们显式初始化为相同的可变对象(例如使用 a = b = [] ),对象的身份永远不会相等,例如:
        a = []; b = []
    a is b # always evaluates to False
    再次,在 the documentation ,这是指定的:

    after a = 1; b = 1, a and b may or may not refer to the same object with the value one, depending on the implementation, but after c = []; d = [], c and d are guaranteed to refer to two different, unique, newly created empty lists.

    关于python - `is` 运算符在处理非缓存整数时出现意外行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34147515/

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