9

我已经搜索了网络和堆栈溢出问题,但无法找到这个问题的答案。我所做的观察是,在 Python 2.7.3 中,如果您为两个变量分配相同的单个字符串,例如

>>> a = 'a'
>>> b = 'a'
>>> c = ' '
>>> d = ' '

然后变量将共享相同的引用:

>>> a is b
True
>>> c is d
True

对于一些较长的字符串也是如此:

>>> a = 'abc'
>>> b = 'abc'
>>> a is b
True
>>> '  ' is '  '
True
>>> ' ' * 1 is ' ' * 1
True

但是,在很多情况下(出乎意料地)没有共享引用:

>>> a = 'a c'
>>> b = 'a c'
>>> a is b
False
>>> c = '  '
>>> d = '  '
>>> c is d
False
>>> ' ' * 2 is ' ' * 2
False

有人可以解释一下原因吗?

我怀疑解释器和/或某些缓存机制可能会进行简化/替换,这些缓存机制利用 python 字符串在某些特殊情况下是不可变的这一事实进行优化,但我知道什么?我尝试使用 str 构造函数和 copy.deepcopy 函数制作字符串的深层副本,但字符串仍然不一致地共享引用。

我遇到问题的原因是因为我在一些单元测试中检查对字符串的引用是否不相等,我正在为新型 python 类的克隆方法编写。

4

3 回答 3

8

何时缓存和重用字符串的细节取决于实现,可以从 Python 版本更改为 Python 版本,并且不能依赖。如果要检查字符串是否相等,请使用==,而不是is

在 CPython(最常用的 Python 实现)中,源代码中出现的字符串字面量始终是 intern,因此如果相同的字符串字面量在源代码中出现两次,它们最终将指向同一个字符串对象。在 Python 2.x 中,您还可以调用内置函数intern()来强制执行特定字符串,但实际上您不应该这样做。

编辑关于检查属性是否在实例之间不正确共享的实际目的:这种检查仅对可变对象有用。对于不可变类型的属性,共享对象和非共享对象之间没有语义差异。您可以通过使用从测试中排除不可变类型

Immutable = basestring, tuple, numbers.Number, frozenset
# ...
if not isinstance(x, Immutable):    # Exclude types known to be immutable

请注意,这也将排除包含可变对象的元组。如果你想测试这些,你需要递归地下降到元组中。

于 2012-07-23T11:37:37.440 回答
5

在 CPython 中,作为实现细节,空字符串是 shared,其代码点在 Latin-1 范围内的单字符字符串也是如此。你不应该依赖这个,因为它可以绕过这个特性。

您可以使用;请求要实习的字符串 sys.intern这在某些情况下会自动发生:

通常,Python 程序中使用的名称是自动实习的,用于保存模块、类或实例属性的字典具有实习键。

sys.intern暴露出来,以便您可以使用它(在分析之后!)以提高性能:

留置字符串对于在字典查找中获得一点性能很有用——如果字典中的键被留存,并且查找键被留存,则键比较(在散列之后)可以通过指针比较而不是字符串比较来完成。

请注意,这intern是 Python 2 中的内置函数。

于 2012-07-23T11:53:04.550 回答
4

我认为这是一个实现和优化的事情。如果字符串很短,它们可以(并且经常?)“共享”,但你不能依赖它。一旦你有更长的字符串,你会发现它们是不一样的。

In [2]: s1 = 'abc'
In [3]: s2 = 'abc'

In [4]: s1 is s2
Out[4]: True

更长的字符串

In [5]: s1 = 'abc this is much longer'
In [6]: s2 = 'abc this is much longer'

In [7]: s1 is s2
Out[7]: False

用于==比较字符串(而不是运算符is)。

--

OP 的观察/假设(在下面的评论中)这可能是由于令牌的数量似乎得到以下支持:

In [12]: s1 = 'a b c'
In [13]: s2 = 'a b c'

In [14]: s1 is s2
Out[14]: False

如果与abc上面的初始示例相比。

于 2012-07-23T11:38:15.863 回答