11

可能重复:
Python“is”运算符对整数的行为异常

我偶然发现了以下Python怪事:

>>> two = 2
>>> ii = 2

>>> id(two) == id(ii)
True
>>> [id(i) for i in [42,42,42,42]]
[10084276, 10084276, 10084276, 10084276]

>>> help(id)
Help on built-in function id in module __builtin__:

id(...)
    id(object) -> integer

    Return the identity of an object.  This is guaranteed to be unique among
    simultaneously existing objects.  (Hint: it's the object's memory address.)
  1. 每个数字都是唯一的对象吗?
  2. 不同的变量是否持有相同的元素值(例如,两个,ii)同一个对象?
  3. Python生成的数字的id是如何产生的?
  4. 在上面的例子中,两个和 ii 是指向一个保存值为 2 的存储单元的指针吗?那将是非常奇怪的。

帮我解开这个身份危机。

还有一些怪事:

>>> a,b=id(0),id(1)
>>> for i in range(2,1000):
   a,b=b,id(i)
   if abs(a-b) != 12:
    print('%i:%i -> %i' % (i,a,b))

上面的代码检查连续整数的 id 是否也是连续的,并打印出异常:

77:10083868 -> 10085840
159:10084868 -> 10086840
241:10085868 -> 10087840
257:10087660 -> 11689620
258:11689620 -> 11689512
259:11689512 -> 11689692
260:11689692 -> 11689548
261:11689548 -> 11689644
262:11689644 -> 11689572
263:11689572 -> 11689536
264:11689536 -> 11689560
265:11689560 -> 11689596
266:11689596 -> 11689656
267:11689656 -> 11689608
268:11689608 -> 11689500
331:11688756 -> 13807288
413:13806316 -> 13814224
495:13813252 -> 13815224
577:13814252 -> 13816224
659:13815252 -> 13817224
741:13816252 -> 13818224
823:13817252 -> 13819224
905:13818252 -> 13820224
987:13819252 -> 13821224

请注意,模式从 413 开始出现。也许这是由于在每个新内存页面的开头都有一些巫术会计。

4

5 回答 5

9

介于 -1 和 255(?) 之间的整数以及字符串文字都被保留。源中的每个实例实际上代表同一个对象。

在 CPython 中,结果id()是 PyObject 进程空间中的地址。

于 2010-01-13T17:47:20.980 回答
8

完全允许 ​​Python 的每个实现在任何程度上优化(包括....根本没有;-)不可变对象(例如数字、元组和字符串)的标识和分配 [[不存在可变对象的这种自由度,例如列表、字典和集合]]。

在两个不可变对象引用a和之间b,所有实现必须保证:

  1. id(a) == id(b), AKA a is b, 必须总是暗示a == b
  2. 因此a != b必须始终暗示id(a) != id(b)AKAa is not b

特别注意没有约束,即使对于不可变类型,也a == b必须暗示a is b(即 that id(a) == id(b))。只做None那个保证(所以你总是可以测试if x is None:而不是if x == None:)。

id当前的 CPython 实现通过“合并”(具有单个分配,因此具有单个, for)特定范围内的小整数以及其文字在给定中多次出现的内置不可变类型对象来利用这些自由度函数(例如,如果您的函数f出现四次文字'foobar',它们都将引用函数常量中的单个字符串实例,'foobar'与存储该常量的四个相同但单独副本的允许实现相比节省了一点空间)。

所有这些实现方面的考虑对 Python 编码人员来说都不太感兴趣(除非您正在处理 Python 实现,或者至少是与特定实现紧密绑定的东西,例如调试系统)。

于 2010-01-13T19:08:02.657 回答
4

您的第四个问题,“在上面的示例中,两个和 ii 是指向保存值 2 的内存单元的指针吗?这将非常奇怪”,这确实是理解整个事情的关键。

如果您熟悉 C 等语言,那么 Python“变量”的工作方式并不相同。AC 变量声明如:

int j=1;
int k=2;
k += j;

说:“编译器,在堆栈上为我保留两个内存区域,每个都有足够的空间来保存一个整数,并记住一个为'j',另一个为'k'。然后用值'1'填充j和值为'2'的k。” 在运行时,代码说“取 k 的整数内容,将 j 的整数内容相加,并将结果存储回 k。”

Python中看似等价的代码:

j = 1
k = 2
k += j

说一些不同的东西:“Python,查找称为'1'的对象,并创建一个名为'j'的标签指向它。查找称为'2'的对象,并创建一个名为'k'的标签指向它现在查找对象'k'指向('2'),查找对象'j'指向('1'),然后将'k'指向执行'add'操作产生的对象就这两个。”

反汇编这段代码(使用dis模块)很好地展示了这一点:

  2           0 LOAD_CONST               1 (1)
              3 STORE_FAST               0 (j)

  3           6 LOAD_CONST               1 (2)
              9 STORE_FAST               1 (k)

  4          12 LOAD_FAST                1 (k)
             15 LOAD_FAST                0 (j)
             18 INPLACE_ADD
             19 STORE_FAST               1 (k)

所以是的,Python“变量”是指向对象的标签,而不是可以填充数据的 容器。

其他三个问题都是关于“Python 何时从一段代码创建新对象,何时重用已有的对象?”的变体。后者称为“实习”;它发生在较小的整数和字符串上,看起来(对 Python 来说)它们可能是符号名称。

于 2010-01-13T18:23:50.657 回答
2

你应该非常小心这些类型的调查。您正在研究该语言实现的内部结构,但不能保证这些内部结构。帮助id很明确:两个不同对象的数字不同,同一个对象的数字相同。作为一个实现细节,在 CPython 中它是对象的内存地址。CPython 可能会决定随时更改此细节。

小整数被实习到同一分配时间的细节也是一个随时可能改变的细节。

此外,如果您从 CPython 切换到 Jython、PyPy 或 IronPython,除了id().

于 2010-01-13T17:55:49.683 回答
1

不是每个数字都是唯一的对象,有些数字是 CPython 解释器的优化细节。 不要依赖这种行为。就此而言,永远不要使用is来测试是否相等。仅is在您绝对确定需要完全相同的对象时使用。

于 2010-01-13T18:03:08.980 回答