Python如何确定两个字符串是否相同?

15

我尝试理解Python字符串何时相同(即共享相同的内存位置)。然而,在我的测试中,当两个相等的字符串变量共享相同的内存时,似乎没有明显的解释:

import sys
print(sys.version) # 3.4.3

# Example 1
s1 = "Hello"
s2 = "Hello"
print(id(s1) == id(s2)) # True

# Example 2
s1 = "Hello" * 3
s2 = "Hello" * 3
print(id(s1) == id(s2)) # True

# Example 3
i = 3
s1 = "Hello" * i
s2 = "Hello" * i
print(id(s1) == id(s2)) # False

# Example 4
s1 = "HelloHelloHelloHelloHello"
s2 = "HelloHelloHelloHelloHello"
print(id(s1) == id(s2)) # True

# Example 5
s1 = "Hello" * 5
s2 = "Hello" * 5
print(id(s1) == id(s2)) # False

字符串是不可变的,据我所知,Python 尝试重用现有的不可变对象,通过让其他变量指向它们,而不是在内存中创建具有相同值的新对象。

考虑到这一点,示例 1 显然返回 True
对我来说,示例 2 仍然很明显返回 True

对于我来说,示例 3 并不明显返回 False - 我没有像在 示例 2 中那样做吗?!?

我偶然发现了这个 SO 问题:
为什么在 Python 中使用 '==' 或 'is' 比较字符串有时会产生不同的结果?

我阅读了http://guilload.com/python-string-interning/(尽管我可能没有完全理解),并想 - 好吧,也许“intern”字符串取决于长度,所以我在 示例 4 中使用了HelloHelloHelloHelloHello。结果是 True

让我感到困惑的是,只是用一个更大的数字做与 示例 4 相同的事情(但实际上会返回相同的字符串) - 然而这次结果是 False?!?

我真的不知道 Python 如何决定是否使用相同的内存对象,或何时创建新对象。

有任何官方来源可以解释这种行为吗?


我不明白为什么示例2和5应该给出不同的结果。 - Mohit Motwani
4
你为什么关心这个?如果你想执行这个操作,只需要使用sys.intern()对字符串进行内部化处理即可。否则,就像你所知道的,规则是特定于实现的,因此不被Python文档记录。 - Chris_Rands
3
您提供的第二个链接详细介绍了该问题。很可能一个 Stack Overflow 的回答不会比那个链接更好。您问题中的最后一句话实际上是与主题无关的(虽然这无疑不是您的本意)。 - John Coleman
2
可能是Python字符串驻留的重复问题。 - Chris_Rands
你链接的guilload博客文章中有你问题的答案:“你必须承认很难确定一个字符串是本地内部化还是不是。所以让我们阅读更多的CPython代码并找出答案!” - Stop harming Monica
2个回答

10

来自你发布的链接

避免大型.pyc文件

那么,为什么'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'不等于True呢?您是否记得在所有包中遇到的.pyc文件?嗯,Python字节码存储在这些文件中。如果有人编写了像这样的内容['foo!'] * 10**9会发生什么?结果的.py文件将非常庞大!为了避免这种现象,如果其长度超过20,则通过窥视孔优化生成的序列将被丢弃。

如果您有字符串"HelloHelloHelloHelloHello",Python必须将其存储为它本身(要求解释器检测字符串中的重复模式以节省空间可能太多)。但是,当涉及可以在解析时计算的字符串值(例如"Hello" * 5)时,Python将其评估为所谓的“窥视孔优化”的一部分,该优化可以决定是否值得预先计算字符串。由于len("Hello" * 5) > 20,因此解释器保持不变,以避免存储太多长字符串。

编辑:

这个问题所示,您可以在源代码中peephole.c的函数fold_binops_on_constants中检查,接近结尾您将看到:

// ...
} else if (size > 20) {
    Py_DECREF(newconst);
    return -1;
}

编辑2:

实际上,该优化代码最近已经移动到Python 3.7的AST优化器中,因此现在您需要查看ast_opt.c,函数fold_binop,该函数现在调用函数safe_multiply,它检查字符串的长度是否超过了新定义的最大字符串大小为4096。因此,似乎下一个版本的限制已经显着提高。


1
在示例2中:
# Example 2
s1 = "Hello" * 3
s2 = "Hello" * 3
print(id(s1) == id(s2)) # True

在这里,s1和s2的值在编译时被评估。因此,这将返回true。
在示例3中:
# Example 3
i = 3
s1 = "Hello" * i
s2 = "Hello" * i
print(id(s1) == id(s2)) # False

在运行时,s1和s2的值会被评估,并且结果不会自动内部化,因此这将返回false。这是为了避免通过在运行时创建“HelloHelloHello”字符串来进行过度的内存分配。
如果您手动内部化,它将返回True。
i = 3
s1 = "Hello" * i
s2 = "Hello" * i
print(id(intern(s1)) == id(intern(s2))) # True

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接