Python 字符串身份悖论
我完全被这件事缠住了Python 字符串身份悖论,python,string,python-internals,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
>>> 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.\uuuu getitem\uuuu
有一个隐藏的特性?有人能解释一下吗
但这并不是我惊讶的结束:
>>> s = chr(8263)
>>> t = s
>>> print(t is s, id(t) == id(s))
True True
这一点很清楚:t
是s
的别名,因此它们表示相同的对象,并且标识一致。但同样,如何实现以下目标:
>>> print(t[0] is s[0])
False
s
和t
是同一个对象,那又怎样
但更糟糕的是:
>>> print(id(t[0]) == id(s[0]))
True
t[0]
和s[0]
未被垃圾收集,被is
操作符视为同一对象,但ID不同?有人能解释一下吗?是比较身份和比较值。检查这个
每个对象都有一个标识、一个类型和一个值。物体的身份
一旦被创造,就永远不会改变;你可以把它看作是
对象在内存中的地址。“is”运算符比较
两个对象;函数的作用是:返回一个表示其
标识(当前作为其地址实现)。对象的类型是
也是不变的
这里有两点需要说明
首先,Python确实使用\uu getitem\uuu
调用创建了一个新字符,但前提是该字符的序数值大于256
例如:
>>> s = chr(256)
>>> s[0] is s
True
>>> t = chr(257)
>>> t[0] is t
False
这是因为在内部,编译后的函数检查单个chracter的序数值,如果该值小于等于256,则调用。这允许共享一些单个字符串。否则,将创建一个新的unicode对象
第二个问题涉及垃圾收集,并表明解释器可以非常快速地重用内存地址。当你写作时:
>>> s = t # = chr(257)
>>> t[0] is s[0]
False
Python首先创建两个新的单字符串,然后比较它们的内存地址。它们有不同的地址(根据上面的解释,我们有不同的对象),因此将对象与进行比较将返回False
另一方面,我们可以看到看似矛盾的情况:
>>> id(t[0]) == id(s[0])
True
但这是因为解释器在稍后创建新字符串s[0]
时会快速重用t[0]
的内存地址
如果您检查此行生成的字节码(例如,使用dis
-见下文),您会看到每一方的地址都是一个接一个地分配的(创建一个新的字符串对象,然后对其调用id
)
只要返回id(t[0])
,对对象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
您的char实际上是一个单元素字符串,因此是的,通过索引访问char会创建长度为1的新字符串。我得到这个-ValueError:chr()arg不在范围(256)
当我运行这个-s=chr(8263)
时,如何解释重复的t[0]是s[0]->False
即使id(t[0])==id(s[0])->True
?@CrakC可能是因为您使用的是Python 2。unichr
标准功能将完成此任务。@P.Ortiz没错!Python 3中的行为有何不同?感谢您提供的信息丰富且令人信服的回答。但它提出了一个问题:它是否与字符串的不变性相矛盾?根据定义,字符串s
的不变性意味着s[valid_index]
对象在字符串生命周期内不会更改?@P.Ortiz:没问题,很高兴它有所帮助。如果我理解正确,那么就我所见,这与字符串对象的不变性并不矛盾。对于第一部分,s[i]
似乎只是检查s
的第i个字符是否在0-256范围内,如果在0-256范围内,则返回由chr(i)
给出的单例字符串对象。对于比较id
部分,就Python而言,t[0]
的生命周期已经结束,因此s[0]
可以自由地占用它所在的内存。没有字符串对象发生变化。我想说得更清楚一些:Python Ref.docs解释道:“当我们讨论容器的可变性时,只会暗示直接包含的对象的标识。”因此,现在,假设您有以下字符串:s=chr(8263)
。如果在s
上执行索引,例如一个无辜的赋值为x=s[0]
,则s
中第一个项目的标识已更改(str.getitem
返回一个新值,如您的响应中所述)。我的意思是,str.\uuu getitem\uuu
是一种变异方法(字符串状态已经改变)。但是,不可变对象没有任何变异方法。写入x=s[0]
根本不会变异或改变s
。如果s
只是chr(8263)
给出的单个字符,则x=s[0]
在内存中创建一个新的字符串对象,并为其指定名称x
。因此,现在s[0]
是与s
相同的unicode字符的表示形式,但它存在于内存中的新地址(它具有不同的标识)。string对象s
没有以任何方式进行更改。事实上,正如您所说:它具有不同的标识,这是我的问题(回想一下Python文档所解释的:“当我们讨论容器的易变性时,只暗示了直接包含的对象的标识