Python 关于更改不可变字符串的id
关于类型为Python 关于更改不可变字符串的id,python,string,immutability,python-internals,Python,String,Immutability,Python Internals,关于类型为str(在python 2.7中)的对象的id,我感到困惑。str类型是不可变的,因此我希望一旦创建它,它将始终具有相同的id。我相信我的词组表达得不太好,所以我将发布一个输入和输出序列的示例 >>> id('so') 140614155123888 >>> id('so') 140614155123848 >>> id('so') 140614155123808 所以在这期间,它一直在变化。但是,在变量指向该字符串后,情况会发生
str
(在python 2.7中)的对象的id
,我感到困惑。str
类型是不可变的,因此我希望一旦创建它,它将始终具有相同的id
。我相信我的词组表达得不太好,所以我将发布一个输入和输出序列的示例
>>> id('so')
140614155123888
>>> id('so')
140614155123848
>>> id('so')
140614155123808
所以在这期间,它一直在变化。但是,在变量指向该字符串后,情况会发生变化:
>>> so = 'so'
>>> id('so')
140614155123728
>>> so = 'so'
>>> id(so)
140614155123728
>>> not_so = 'so'
>>> id(not_so)
140614155123728
因此,一旦变量持有该值,它就会冻结id。实际上,在del so
和del not_so
之后,id('so')
的输出再次开始改变
这与(小)整数的行为不同
我知道不变性和拥有相同的id
之间没有真正的联系;不过,我仍在试图找出这种行为的根源。我相信熟悉python内部结构的人不会像我那么惊讶,所以我正试图达到同样的目的
更新
使用不同的字符串尝试相同的操作会得到不同的结果
>>> id('hello')
139978087896384
>>> id('hello')
139978087896384
>>> id('hello')
139978087896384
现在它是相等的…在您的第一个示例中,每次都会创建字符串的新实例
'so'
,因此id不同
在第二个示例中,您将字符串绑定到变量,然后Python可以维护字符串的共享副本。此行为特定于Python交互式shell。如果我将以下内容放入.py文件中:
print id('so')
print id('so')
print id('so')
然后执行它,我会收到以下输出:
2888960
2888960
2888960
相同的常量(即相同的字符串对象)被加载了3次,因此ID是相同的。因此,虽然Python不能保证插入字符串,但它会频繁地重用相同的字符串,
is
可能会误导用户。重要的是要知道,您不应该检查id
或is
字符串是否相等
为了演示这一点,我发现了至少在Python 2.6中强制使用新字符串的一种方法:
>>> so = 'so'
>>> new_so = '{0}'.format(so)
>>> so is new_so
False
下面是更多的Python探索:
>>> id(so)
102596064
>>> id(new_so)
259679968
>>> so == new_so
True
CPython不承诺默认情况下将所有字符串都插入内部,但在实践中,Python代码库中的很多地方都会重用已经创建的字符串对象。许多Python内部使用(相当于的C语言)显式地插入Python字符串,但是除非遇到其中一种特殊情况,否则两个相同的Python字符串文本将生成不同的字符串 Python还可以自由地重用内存位置,并且Python还将通过在编译时将不可变文本和字节码存储在代码对象中一次来优化它们。Python REPL(交互式解释器)还将最新的表达式结果存储在
名称中,这会让事情变得更加混乱
因此,您将不时看到相同的id出现
仅运行REPL中的行id()
,需要经过几个步骤:
>>> compile("id('foo')", '<stdin>', 'single').co_consts
('foo', None)
v
,执行:
if(所有名称字符(v)){
PyObject*w=v;
PyUnicode_Interniplace(&v);
如果(w!=v){
PyTuple\u SET\u项(tuple,i,v);
修改=1;
}
}
在哪里记录为
/*所有字符名称:正确的iff匹配[a-zA-Z0-9]**/
由于您创建了符合该标准的字符串,因此它们被插入,这就是为什么您在第二次测试中看到相同的ID被用于'so'
字符串:只要对插入版本的引用仍然存在,插入将导致将来的'so'
文本重用插入的字符串对象,即使在新的代码块中,也要绑定到不同的标识符。在您的第一个测试中,您不保存对字符串的引用,因此在可以重用之前,将丢弃插入的字符串
顺便说一下,您的新名称so='so'
将字符串绑定到包含相同字符的名称。换句话说,您正在创建一个名称和值相等的全局变量。由于Python同时使用标识符和限定常量,因此最终会对标识符及其值使用相同的字符串对象:
>>> compile("so = 'so'", '<stdin>', 'single').co_names[0] is compile("so = 'so'", '<stdin>', 'single').co_consts[0]
True
Python编译器使用(Python版本<3.7)或更强大的(3.7及更高版本)预计算(折叠)包含常量的简单表达式的结果。peepholder将其输出限制为长度不超过20的序列(以防止代码对象膨胀和内存使用),而AST优化器对4096个字符的字符串使用单独的限制。这意味着,如果生成的字符串符合当前Python版本的优化器限制,则连接仅由名称字符组成的较短字符串仍然会导致插入字符串
例如,在Python3.7上,'foo'*20
将产生一个内部字符串,因为常量折叠将其转换为单个值,而在Python3.6或更早版本上,只有'foo'*6
将被折叠:
>>> import dis, sys
>>> sys.version_info
sys.version_info(major=3, minor=7, micro=4, releaselevel='final', serial=0)
>>> dis.dis("'foo' * 20")
1 0 LOAD_CONST 0 ('foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo')
2 RETURN_VALUE
及
更简单的
>>> compile("so = 'so'", '<stdin>', 'single').co_names[0] is compile("so = 'so'", '<stdin>', 'single').co_consts[0]
True
>>> 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
>>> import dis, sys
>>> sys.version_info
sys.version_info(major=3, minor=7, micro=4, releaselevel='final', serial=0)
>>> dis.dis("'foo' * 20")
1 0 LOAD_CONST 0 ('foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo')
2 RETURN_VALUE
>>> dis.dis("'foo' * 6")
1 0 LOAD_CONST 2 ('foofoofoofoofoofoo')
2 RETURN_VALUE
>>> dis.dis("'foo' * 7")
1 0 LOAD_CONST 0 ('foo')
2 LOAD_CONST 1 (7)
4 BINARY_MULTIPLY
6 RETURN_VALUE