默认情况下,CPython不嵌入字符串,但在实践中,Python代码库中的很多地方都会重复使用已经创建的字符串对象。许多Python内部使用(C等效的)

intern() function call来明确地实现Python字符串,但是一般来说,Python字符串文字每次都会创建一个新的字符串对象。

Python也可以自由地重用内存位置,Python也将通过在编译时将代码对象中的字节码存储一次来使不变值保持不变。 Python REPL(交互式解释器)也将最新的表达式结果存储在_名称中,这样就会使事情更加混乱。

因此,您将不时看到相同的标题。

在REPL中运行行id(< string literal>)可以通过以下几个步骤:

>该行被编译,其中包括为字符串对象创建一个常量:

>>> compile("id('foo')", '', 'single').co_consts

('foo', None)

这显示了存储的常量与编译的字节码;在这种情况下,字符串’foo’和无单例。

>执行时,字符串从代码常量加载,id()返回内存位置。生成的int值绑定到_,并打印:

>>> import dis

>>> dis.dis(compile("id('foo')", '', 'single'))

1 0 LOAD_NAME 0 (id)

3 LOAD_CONST 0 ('foo')

6 CALL_FUNCTION 1

9 PRINT_EXPR

10 LOAD_CONST 1 (None)

13 RETURN_VALUE

>代码对象没有引用,引用计数下降到0,代码对象被删除。因此,字符串对象也是如此。

如果您重新运行相同的代码,Python可能会为新的字符串对象重新使用相同的内存位置。如果您重复此代码,通常会导致打印相同的内存地址。这取决于你用Python内存做什么。

ID重用是不可预测的;如果在此期间,垃圾收集器运行以清除循环引用,则可以释放其他内存,并获得新的内存地址。

接下来,Python编译器还会将存储为常量的任何Python字符串进行实习,只要它是一个有效的标识符。 Pyt

hon code object factory function PyCode_New将实习只包含字母,数字或下划线的任何字符串对象:
/* Intern selected string constants */
for (i = PyTuple_Size(consts); --i >= 0; ) {
PyObject *v = PyTuple_GetItem(consts, i);
if (!PyString_Check(v))
continue;
if (!all_name_chars((unsigned char *)PyString_AS_STRING(v)))
continue;
PyString_InternInPlace(&PyTuple_GET_ITEM(consts, i));
}

由于您创建了符合该条件的字符串,因此它们被实体化,这就是为什么您看到与“so”字符串一样使用的ID,即使重新创建并绑定到不同的标识符。

顺便说一句,你的新名称so =’so’将字符串绑定到包含相同字符的名称。换句话说,您正在创建名称和值相等的全局。由于Python对标识符和限定性常量进行实习,因此您最终将对标识符及其值使用相同的字符串对象:

>>> compile("so = 'so'", '', 'single').co_names[0] is compile("so = 'so'", '', 'single').co_consts[0]

True

如果您创建不是代码对象常量的字符串,或者包含字母数字下划线范围之外的字符,您将看到id()值不被重用:

>>> some_var = 'Look ma, spaces and punctuation!'
>>> some_other_var = 'Look ma, spaces and punctuation!'
>>> id(some_var)
4493058384
>>> id(some_other_var)
4493058456
>>> foo = 'Concatenating_' + 'also_helps_if_long_enough'
>>> bar = 'Concatenating_' + 'also_helps_if_long_enough'
>>> foo is bar
False
>>> foo == bar
True

Python窥视孔优化器预先计算简单表达式的结果,但如果这导致长于20的序列,则忽略输出(以防止膨胀的代码对象和内存使用);因此,如果结果为20个字符或更短,则连接仅由名称字符组成的较短字符串仍然可以导致内部字符串。