About the changing id of an immutable string
关于EDOCX1(在python 2.7中)类型的对象的
1 2 3 4 5 6 | >>> id('so') 140614155123888 >>> id('so') 140614155123848 >>> id('so') 140614155123808 |
所以在这期间,它一直在变化。但是,在有一个变量指向该字符串之后,情况会发生变化:
1 2 3 4 5 6 7 8 9 | >>> so = 'so' >>> id('so') 140614155123728 >>> so = 'so' >>> id(so) 140614155123728 >>> not_so = 'so' >>> id(not_so) 140614155123728 |
所以看起来它冻结了ID,一旦变量保存了这个值。实际上,在
这与(小)整数的行为不同。
我知道不变性和拥有相同的
用不同的字符串尝试相同的方法会得到不同的结果…
1 2 3 4 5 6 | >>> id('hello') 139978087896384 >>> id('hello') 139978087896384 >>> id('hello') 139978087896384 |
现在它是平等的…
cpython不承诺在默认情况下对字符串进行实习生,但是在实践中,python代码库中的很多地方都会重用已经创建的字符串对象。许多python内部使用(c等价于)
python还可以自由地重用内存位置,python还可以通过在编译时将不可变的文本与代码对象中的字节码一起存储一次来优化它们。python repl(交互式解释器)还将最新的表达式结果存储在
因此,您会不时看到相同的ID出现。
仅在repl中运行行
将编译该行,其中包括为字符串对象创建常量:
1 2 | >>> compile("id('foo')", '<stdin>', 'single').co_consts ('foo', None) |
这将显示已编译字节码的存储常量;在本例中,是字符串
执行时,从代码常量加载字符串,并且
1 2 3 4 5 6 7 8 | >>> import dis >>> dis.dis(compile("id('foo')", '<stdin>', '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字符串。python code对象工厂函数pycode_new将实习仅包含ASCII字母、数字或下划线的任何字符串对象:
1 2 3 4 5 6 7 8 9 | /* 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)); } |
由于您创建了符合该标准的字符串,因此它们被实习生,这就是为什么您在第二个测试中看到相同的ID被用于
顺便说一下,您的新名称
1 2 | >>> compile("so = 'so'", '<stdin>', 'single').co_names[0] is compile("so = 'so'", '<stdin>', 'single').co_consts[0] True |
如果创建的字符串不是代码对象常量,或者包含字母+数字+下划线范围之外的字符,则会看到
1 2 3 4 5 6 7 8 9 10 11 12 | >>> 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 peephole优化器确实预先计算简单表达式的结果,但是如果这导致序列长度超过20,则会忽略输出(以防止代码对象膨胀和内存使用);因此,如果结果是20个字符或短字符串,则连接仅由名称字符组成的较短字符串仍然会导致插入字符串。R.
此行为特定于Python交互式shell。如果我将以下内容放入.py文件中:
1 2 3 | print id('so') print id('so') print id('so') |
并执行它,我收到以下输出:
1 2 3 | 2888960 2888960 2888960 |
在cpython中,字符串文字被视为常量,我们可以在上面代码段的字节码中看到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 2 0 LOAD_GLOBAL 0 (id) 3 LOAD_CONST 1 ('so') 6 CALL_FUNCTION 1 9 PRINT_ITEM 10 PRINT_NEWLINE 3 11 LOAD_GLOBAL 0 (id) 14 LOAD_CONST 1 ('so') 17 CALL_FUNCTION 1 20 PRINT_ITEM 21 PRINT_NEWLINE 4 22 LOAD_GLOBAL 0 (id) 25 LOAD_CONST 1 ('so') 28 CALL_FUNCTION 1 31 PRINT_ITEM 32 PRINT_NEWLINE 33 LOAD_CONST 0 (None) 36 RETURN_VALUE |
相同的常量(即相同的字符串对象)被加载3次,因此ID是相同的。
在第一个示例中,每次都会创建字符串
在第二个示例中,您将字符串绑定到一个变量,然后python可以维护字符串的共享副本。
理解行为的一个更简单的方法是检查以下数据类型和变量。
"字符串特性"一节以特殊字符为例说明您的问题。
因此,虽然不能保证python在字符串之间进行交互,但它会经常重用同一个字符串,并且
为了证明这一点,我发现了一种在Python2.6中强制使用新字符串的方法:
1 2 3 4 | >>> so = 'so' >>> new_so = '{0}'.format(so) >>> so is new_so False |
这里还有一点关于Python的探索:
1 2 3 4 5 6 | >>> id(so) 102596064 >>> id(new_so) 259679968 >>> so == new_so True |