字符串字符标识悖论

P. *_*tiz 11 python string python-internals

我完全坚持这个

>>> s = chr(8263)
>>> x = s[0]
>>> x is s[0]
False
Run Code Online (Sandbox Code Playgroud)

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

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

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

但这并不是我的惊喜:

>>> s = chr(8263)
>>> t = s
>>> print(t is s, id(t) == id(s))
True True
Run Code Online (Sandbox Code Playgroud)

这很清楚:t是别名s,所以它们代表相同的对象和身份重合.但同样,以下是可能的:

>>> print(t[0] is s[0])
False
Run Code Online (Sandbox Code Playgroud)

s并且t是同一个对象那又怎样?

但更糟糕的是

>>> print(id(t[0]) == id(s[0]))
True
Run Code Online (Sandbox Code Playgroud)

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

Ale*_*ley 7

这里有两点要做.

首先,Python确实通过调用创建了一个字符__getitem__,但前提是该字符的序数值大于 256.

例如:

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

>>> t = chr(257)
>>> t[0] is t
False
Run Code Online (Sandbox Code Playgroud)

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

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

>>> s = t # = chr(257)
>>> t[0] is s[0]
False
Run Code Online (Sandbox Code Playgroud)

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

另一方面,我们可以看到一个看似矛盾的情况:

>>> id(t[0]) == id(s[0])
True
Run Code Online (Sandbox Code Playgroud)

但这是因为解释器很快就会重新使用t[0]s[0]在以后创建新字符串时的内存地址.

如果你检查这一行产生的字节码(例如,dis见 - 见下文),你会看到每一面的地址是一个接一个地分配的(创建一个新的字符串对象,然后id在其上调用).

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


这是id(t[0]) == id(s[0])我注释的行的反汇编字节码.

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

  2           0 LOAD_GLOBAL              0 (id)
              3 LOAD_GLOBAL              1 (t)
              6 LOAD_CONST               1 (0)
              9 BINARY_SUBSCR                     # t[0] is created
             10 CALL_FUNCTION            1        # id(t[0]) is computed...
                                                  # ...lifetime of string t[0] over
             13 LOAD_GLOBAL              0 (id)
             16 LOAD_GLOBAL              2 (s)
             19 LOAD_CONST               1 (0)
             22 BINARY_SUBSCR                     # s[0] is created...
                                                  # ...free to reuse t[0] memory
             23 CALL_FUNCTION            1        # id(s[0]) is computed
             26 COMPARE_OP               2 (==)   # the two ids are compared
             29 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)