Python身份认证:多重人格障碍,需要代码缩减

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. 对于持有相同原始值的不同变量(例如,两个two、ii),它们是否为同一对象?
  3. Python中数字id的生成方式是什么?
  4. 在上面的示例中,two和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开始出现了一种模式。也许这是由于每个新的内存页面开始时的某些巫术会计处理所致。


1
这有什么关系?你为什么要问?这会引起什么问题?我不明白这个问题。请澄清一下出了什么问题。 - S.Lott
3
他想要加深自己的理解,这有什么问题吗?有时候,旅程本身比目的地更有价值。 - Simon
1
我不理解这个问题。如果问题不清晰,我无法帮助加深任何理解。 - S.Lott
+1 非常机智的标题。问题也很有趣! - James
5个回答

9

-1到255之间的整数(包括-1和255),以及字符串字面值,都会被池化。源代码中的每个实例实际上都代表着同一个对象。

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


1
这只对CPython是必然的。即使在那里,也没有人保证这不会改变,甚至在语法冻结期间也可能会改变。 - jcdyer
真的。在旧版本中,它只能达到99左右。 - Ignacio Vazquez-Abrams

8
每个Python实现都完全允许优化任何程度(包括......根本没有;-)不可变对象的身份和分配(例如数字,元组和字符串)[可变对象(如列表、字典和集合)不存在此类自由度]。
在两个不可变对象引用a和b之间,所有实现必须保证:
1. id(a)==id(b),也就是a is b,必须始终意味着a == b 2. 因此,a != b必须始终意味着id(a) != id(b),也就是a is not b 特别要注意的是,即使对于不可变类型,也没有约束条件要求a == b意味着a is b(即id(a) == id(b))。只有None能够提供这种保证(因此你可以始终测试if x is None:而不是if x == None:)。
当前的CPython实现利用这些自由度,在一定范围内合并(具有单一分配,因此单一的id)小整数和内置不可变类型对象,其文字在给定函数中出现多次(例如,如果您的函数f具有四个'foobar'文字出现,它们都将引用函数常量中的'foobar'字符串的单个实例,与允许存储该常量的四个相同但不同的副本相比,可以节省一些空间)。
所有这些实现考虑因素对Python程序员来说都不是很重要(除非你正在开发一个Python实现,或者至少是与特定实现密切相关的东西,如调试系统)。

4
你的第四个问题,“在上面的例子中,两个指针ii指向一个存储值为2的内存单元吗?那将是非常奇怪的”,实际上是理解整个问题的关键。
如果你熟悉像C、Python这样的语言,“变量”并不是以同样的方式工作的。例如C语言中的变量声明:
```c int x = 2; ```
这意味着创建一个名为“x”的变量,并将其初始化为2。但是,在Python中,可以使用以下代码完成相同的操作:
```python x = 2 ```
这里没有类型声明,因为Python是一种动态类型语言,它会根据值自动推断类型。在Python中,“变量”只是名称和值之间的映射,而不是指向内存位置的指针。因此,在上面的示例中,两个指针指向存储值为2的相同内存单元确实非常奇怪。
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)可能像符号名称的字符串上。


1
创建一个名为“2”的对象,并创建一个名为“j”的标签,其值应该为“1”。 - Gordon Wrigley

2
这种调查需要非常小心。您正在研究语言实现的内部细节,而这些是不能保证的。关于id的帮助是准确的:对于两个不同的对象,它们的数字会不同,对于相同的对象则相同。作为实现细节,在CPython中它是对象的内存地址。CPython随时可能决定更改这个细节。
小整数被分配到同一内存地址的细节也是可随时更改的细节。
另外,如果您从CPython转到Jython、PyPy或IronPython,则除了id()文件之外,其他都不确定。

1
并不是每个数字都是唯一的对象,事实上只有部分数字是CPython解释器优化细节的结果。 不要 依赖这种行为。就这个问题而言,永远不要使用is测试相等性。只有当你确定需要完全相同的对象时才使用is

2
注意事项:在与None进行比较时,应始终使用is - jcdyer
省略号。虽然你永远不必这样做。 - Ignacio Vazquez-Abrams
3
从PEP-8中直接引用的一般规则是,“当与单例对象进行比较时使用is”。这意味着None、True、False、Ellipsis等对象。然而,通常最好完全省略True和False,以允许进行布尔强制转换。 - Tim Lesher

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接