字符串字符标识悖论

时间:2015-10-27 11:09:55

标签: python string python-internals

我完全坚持这个

>>> s = chr(8263)
>>> x = s[0]
>>> x is s[0]
False

这怎么可能?这是否意味着通过索引访问字符串字符会创建相同字符的新实例?我们来试验一下:

>>> L = [s[0] for _ in range(1000)]
>>> len(set(L))
1
>>> ids = map(id, L)
>>> len(set(ids))
1000
>>>

哎呀浪费字节;)或者它是否意味着str.__getitem__有隐藏的功能?有人可以解释一下吗?

但这并不是我的意外结束:

>>> s = chr(8263)
>>> t = s
>>> print(t is s, id(t) == id(s))
True True

很明显:ts的别名,因此它们代表相同的对象,并且身份重合。但同样,以下是可能的:

>>> print(t[0] is s[0])
False

st是同一个对象,那又是什么?

但更糟糕的是:

>>> print(id(t[0]) == id(s[0]))
True

t[0]s[0]未被垃圾收集,被is运算符视为同一个对象,但具有不同的ID?有人可以解释一下吗?

2 个答案:

答案 0 :(得分:7)

这里有两点要做。

首先,Python确实使用__getitem__调用创建了一个 new 字符,但前提是该字符的序数值大于而不是256。

例如:

>>> s = chr(256)
>>> s[0] is s
True

>>> t = chr(257)
>>> t[0] is t
False

这是因为在内部,编译的getitem函数检查单个字符的序数值,如果该值为256或更小,则调用get_latin1_char。这允许共享一些单字符串。否则,将创建一个新的unicode对象。

第二个问题涉及垃圾收集,并显示解释器可以非常快速地重用内存地址。当你写:

>>> s = t # = chr(257)
>>> t[0] is s[0]
False

Python首先创建两个新的单字符串,然后比较它们的内存地址。它们有不同的地址(我们根据上面的解释有不同的对象),因此将对象与is进行比较会返回False。

另一方面,我们可能会出现看似矛盾的情况:

>>> id(t[0]) == id(s[0])
True

但这是因为解释器在稍后时刻创建新字符串t[0]时会快速重用s[0]的内存地址。

如果检查此行产生的字节码(例如,使用dis - 见下文),您会看到每一侧的地址是一个接一个地分配的(创建一个新的字符串对象然后{{1调用它)。

一旦返回id,对象t[0]的引用就会降为零(我们现在正在对整数进行比较,而不是对象本身)。这意味着id(t[0])可以在以后创建时重用相同的内存地址。

以下是我已注释的行s[0]的反汇编字节码。

您可以看到id(t[0]) == id(s[0])的生命周期在创建t[0]之前结束(没有对它的引用),因此可以重用它的内存。

s[0]

答案 1 :(得分:0)

is比较身份和==比较值。请检查此doc

  

每个对象都有一个标识,一个类型和一个值。对象的身份   一旦创建就永远不会改变;你可能会认为它是   对象在内存中的地址。 'is'运算符比较了。的身份   两个对象; id()函数返回一个表示它的整数   身份(目前作为其地址实施)。对象的类型是   也是不可改变的。