当前位置: 首页 > 面试题库 >

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

祁修诚
2023-03-14
问题内容

在使用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,但是,相反,它们指向一个不同的对象在它之外时。

为什么会这样呢?

注意 :我了解Identity(is)和equal(==)操作之间的区别,如了解Python的“
is”运算符所述。此外,我还知道python正在对“is”运算符中[-5,256]所述的range范围内的整数执行的缓存对于integers的行为异常。

这里 不是这种情况, 因为数字不在该范围内, 我确实 想评估同一性而 不是 相等性。


问题答案:

tl; dr:

如参考手册所述:

块是作为单元执行的一段Python程序文本。以下是块:模块,函数体和类定义。 交互键入的每个命令都是一个块。

这就是为什么,在功能的情况下,你有一个 单一的 代码模块包含一个 单一 的数字文本对象 1000,所以id(a) == id(b)会产生True

在第二种情况下,您有 两个不同的代码对象, 每个 对象 都有它们自己的文字对象1000so id(a) != id(b)

请注意,这种行为并不仅int限于文字,您还会得到类似的结果,例如,float文字。

当然,比较对象(除显式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

我们只对Constantsfunction的条目感兴趣func。在其中,我们可以看到我们有两个值None(始终存在)和1000。我们只有一个
单一的 ,代表的整型实例1000。这是该值ab将要分配被调用函数时。

可以很容易地访问此值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看起来一样,但 有一个根本的区别
:每个代码的对象com1,并com2具有代表字面不同INT实例1000。这就是为什么在这种情况下,当我们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的答案。

注意事项:

  • 链接的语句将评估为的身份检查 True

现在应该更清楚地说明以下内容为何准确地得出True

    >>> a = 1000; b = 1000;
>>> a is b

在这种情况下,通过将两个分配命令链接在一起,我们告诉解释器将它们 一起
编译。与功能对象的情况一样,将只为文字1000创建一个对象,并True在评估时产生一个值。

  • 在模块级别执行将True再次产生:

如前所述,参考手册指出:

…以下是块: 一个模块

因此,适用相同的前提:我们将有一个代码对象(用于模块),因此,将为每个不同的文字存储单个值。

  • 同样 适用于 可变 对象:

这意味着除非我们显式初始化为同一可变对象(例如,使用a = b = []),否则对象的身份永远不会相等,例如:

    a = []; b = []
a is b  # always returns false

同样,在文档中,指定了以下内容:

在a = 1之后;b = 1,取决于实现,a和b可以或可以不使用值1引用同一对象,但是在c = []之后;d =
[],保证c和d引用两个不同的,唯一的,新创建的空列表。



 类似资料:
  • 问题内容: 为什么以下代码在Python中表现异常? 我正在使用Python 2.5.2。尝试使用某些不同版本的Python,Python 2.3.3似乎在99到100之间显示了上述行为。 基于以上所述,我可以假设Python是内部实现的,因此“小”整数的存储方式与大整数的存储方式不同,并且is运算符可以分辨出这种差异。为什么要泄漏抽象?当我事先不知道它们是否为数字时,比较两个任意对象以查看它们是

  • 问题内容: 单元测试模块时遇到一个令人困惑的问题。该模块实际上是在转换值,我想比较这些值。 与和之间存在差异(部分,我谨防差异) 到现在为止,这是我的“问题”: 为什么?至少最后一个让我很困惑。的内部表示和应该相等。与的比较按预期进行。 问题答案: 这与工作方式有关。它检查引用而不是值。如果将任何一个参数分配给相同的对象,则返回。 在这种情况下,它们是不同的实例。并且具有相同的值,但就Python

  • 我读到“因此当使用Integer.ValueOf创建对象或直接为-128到127范围内的整数赋值时,将返回相同的对象。” 但这里的输出是“not” 有人能解释一下吗?

  • 问题内容: 该运营商不匹配变量的值,但这些实例本身。 到底是什么意思 我声明了两个变量,并在两个变量中分配了相同的值,但是当我使用运算符时,它返回。 我需要澄清。这是我的代码。 问题答案: 您误解了操作员的测试内容。它测试两个变量是否指向同一个对象,而不是两个变量具有相同的值。 从操作员文档中: 运算符is和is not对象标识测试:当且仅当和y是相同对象时,才为。 改用运算符: 打印True。x

  • 问题内容: 我有一个简单的SQL查询,该查询在go的数据库/ sql软件包提供的QueryRow方法内。 但是,我收到错误消息看来代码不明白那个仅仅是一个占位符。我怎样才能解决这个问题? 问题答案: PostgreSQL 本机使用带编号的占位符(,…),而不是通常的位置问号。Go接口的文档在其示例中还使用了数字占位符: 似乎Go接口没有像许多接口那样将问号转换为带编号的占位符,因此问号一直到数据库

  • 问题内容: 在下面的python脚本中,为什么要执行第二个断言(即,将0加到257并将结果存储在y中,则x和y成为不同的对象)?谢谢! 问题答案: 整数是不可变的,因此任何更改它们的操作都将导致新的内存位置 正在检查对象的实际内存位置…并且基本上不应该用于检查值的相等性(尽管它可以在某些情况下任意工作)