Python字符串实习


92

尽管这个问题在实际中没有任何实际用途,但我对Python如何进行字符串实习感到好奇。我注意到以下内容。

>>> "string" is "string"
True

这是我所期望的。

您也可以这样做。

>>> "strin"+"g" is "string"
True

那真是太聪明了!

但是您不能这样做。

>>> s1 = "strin"
>>> s2 = "string"
>>> s1+"g" is s2
False

Python为什么不评估s1+"g",并意识到它s2与指向相同的地址相同?最后一个块到底在做什么False呢?

Answers:


95

这是特定于实现的,但是您的解释器可能是内部编译时常量,而不是运行时表达式的结果。

接下来,我使用CPython 2.7.3。

在第二个示例中,表达式"strin"+"g"在编译时求值,并替换为"string"。这使得前两个示例的行为相同。

如果我们检查字节码,我们会发现它们是完全相同的:

  # s1 = "string"
  2           0 LOAD_CONST               1 ('string')
              3 STORE_FAST               0 (s1)

  # s2 = "strin" + "g"
  3           6 LOAD_CONST               4 ('string')
              9 STORE_FAST               1 (s2)

第三个示例涉及运行时串联,其结果不会自动中断:

  # s3a = "strin"
  # s3 = s3a + "g"
  4          12 LOAD_CONST               2 ('strin')
             15 STORE_FAST               2 (s3a)

  5          18 LOAD_FAST                2 (s3a)
             21 LOAD_CONST               3 ('g')
             24 BINARY_ADD          
             25 STORE_FAST               3 (s3)
             28 LOAD_CONST               0 (None)
             31 RETURN_VALUE        

如果要手动intern()执行第三个表达式的结果,则将获得与以前相同的对象:

>>> s3a = "strin"
>>> s3 = s3a + "g"
>>> s3 is "string"
False
>>> intern(s3) is "string"
True

22
根据记载:Python的窥视孔优化将预先计算的常数(算术运算"string1" + "s2"10 + 3*20在编译时,等),但造成的限制序列,只是20元(以防止 [None] * 10**1000从过度扩大你的字节码)。正是这种优化"strin" + "g"陷入了"string"; 结果少于20个字符。
马丁·彼得

13
并明确地说:这里根本没有进行过实习。相反,不变的文字与字节码一起存储为常量。对于代码中使用的名称,确实会进行实习,但不会对程序创建的字符串值进行实习,除非该intern()函数专门进行了实习。
马丁·彼得斯

9
对于那些试图intern在Python 3中查找函数的人-它将移至sys.intern
Timofey Chernousov '17

1

情况1

>>> x = "123"  
>>> y = "123"  
>>> x == y  
True  
>>> x is y  
True  
>>> id(x)  
50986112  
>>> id(y)  
50986112  

情况二

>>> x = "12"
>>> y = "123"
>>> x = x + "3"
>>> x is y
False
>>> x == y
True

现在,您的问题是,为什么情况1的id相同,而情况2的id却不同。
在情况1中,您已将字符串文字分配"123"xy

由于字符串是不可变的,因此对于解释器来说,只存储一次字符串文字并将所有变量指向同一对象是有意义的。
因此,您看到的ID是相同的。

在情况2中,您正在x使用串联进行修改。双方xy具有相同的价值观,但不相同的身份。
两者都指向内存中的不同对象。因此它们有不同,id并且is运算符返回False


既然字符串是不可变的,为什么分配x +“ 3”(并寻找新的位置来存储字符串)却没有分配给与y相同的引用?
nicecatch

因为这样就需要将新字符串与所有现有字符串进行比较;可能是非常昂贵的操作。我想在分配后可以在后台执行此操作以减少内存,但是您最终甚至会遇到陌生的行为:id(x) != id(x)例如,因为字符串是在求值过程中移动的。
DylanYoung

1
@AndreaConte,因为字符串的并发并不会在每次生成一个新字符串时执行查找所有已使用字符串池的额外工作。另一方面,解释器将表达式“优化”x = "12" + "3"x = "123"(在单个表达式中两个字符串文字的串联),因此赋值实际上进行查找并找到与相同的“内部”字符串y = "123"
derenio

实际上,不是分配执行查找,而是源代码中的每个字符串文字都被“内部化”,并且该对象在所有其他地方都被重用。
derenio
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.