【问题标题】:Python string interningPython字符串实习
【发布时间】:2013-03-10 14:26:08
【问题描述】:

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

>>> "string" is "string"
True

正如我所料。

你也可以这样做。

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

这很聪明!

但你不能这样做。

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

为什么 Python 不评估 s1+"g",并意识到它与 s2 相同并将其指向相同的地址?最后一个区块实际上发生了什么让它返回False

【问题讨论】:

    标签: python string python-internals internals string-interning


    【解决方案1】:

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

    以下使用 CPython 3.9.0+。

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

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

      # s1 = "string"
      1           0 LOAD_CONST               0 ('string')
                  2 STORE_NAME               0 (s1)
    
      # s2 = "strin" + "g"
      2           4 LOAD_CONST               0 ('string')
                  6 STORE_NAME               1 (s2)
    

    这个字节码是通过以下方式获得的(在上面打印了几行之后):

    import dis
    
    source = 's1 = "string"\ns2 = "strin" + "g"'
    code = compile(source, '', 'exec')
    print(dis.dis(code))
    

    第三个示例涉及运行时连接,其结果不会自动被实习:

      # s3a = "strin"
      3           8 LOAD_CONST               1 ('strin')
                 10 STORE_NAME               2 (s3a)
    
      # s3 = s3a + "g"
      4          12 LOAD_NAME                2 (s3a)
                 14 LOAD_CONST               2 ('g')
                 16 BINARY_ADD
                 18 STORE_NAME               3 (s3)
                 20 LOAD_CONST               3 (None)
                 22 RETURN_VALUE
    

    这个字节码是通过以下方式获得的(它在上面打印了几行,这些行与上面给出的第一块字节码完全相同):

    import dis
    
    source = (
        's1 = "string"\n'
        's2 = "strin" + "g"\n'
        's3a = "strin"\n'
        's3 = s3a + "g"')
    code = compile(source, '', 'exec')
    print(dis.dis(code))
    

    如果您要手动 sys.intern() 第三个表达式的结果,您将获得与之前相同的对象:

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

    此外,Python 3.9 会为上面的最后两个语句打印警告:

    SyntaxWarning:带有文字的“is”。你是说“==”吗?

    【讨论】:

    • 记录一下:Python 的窥孔优化将在编译时预先计算常量("string1" + "s2"10 + 3*20 等)的算术运算,但会限制生成的 sequences 仅包含 20 个元素(以防止 [None] * 10**1000 过度扩展您的字节码)。正是这种优化将"strin" + "g" 折叠成"string";结果少于 20 个字符。
    • 更清楚一点:这里根本没有实习。相反,不可变的文字与字节码一起存储为常量。实习确实发生在代码中使用的名称,但不适用于程序创建的字符串值,除非intern() 函数专门实习。
    • 对于那些试图在 Python 3 中找到 intern 函数的人 - 它已移至 sys.intern
    【解决方案2】:

    案例 1

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

    案例 2

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

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

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

    在情况 2 中,您正在使用串联修改 xxy 具有相同的值,但身份不同。
    两者都指向内存中的不同对象。因此他们有不同的idis 运算符返回False

    【讨论】:

    • 怎么会,因为字符串是不可变的,分配 x+"3" (并寻找新的位置来存储字符串)不会分配给与 y 相同的引用?
    • 因为这时它需要将新字符串与所有现有字符串进行比较;可能是非常昂贵的操作。我想它可以在分配后在后台执行此操作,以减少内存,但最终会出现更奇怪的行为:例如id(x) != id(x),因为字符串在评估过程中被移动。
    • @AndreaConte 因为字符串的连接并不会在每次生成新字符串时查找所有已使用字符串的池。另一方面,解释器将表达式x = "12" + "3"“优化”为x = "123"(在一个表达式中连接两个字符串文字),因此赋值实际上进行查找并找到与y = "123"相同的“内部”字符串。
    • 实际上,并不是赋值进行查找,而是源代码中的每个字符串文字都被“内部化”并且该对象在所有其他地方被重用。
    猜你喜欢
    • 2013-07-14
    • 1970-01-01
    • 2011-02-11
    • 1970-01-01
    • 2010-09-23
    • 1970-01-01
    • 2015-02-26
    • 2016-09-11
    • 2012-01-31
    相关资源
    最近更新 更多