python中stringvar不能修改_python-关于不可变字符串的更改id

CPython并不承诺默认情况下会内生所有字符串,但是实际上,Python代码库中的许多地方确实会重用已经创建的字符串对象。 许多Python内部使用'foo' * 20函数调用(与C等效)来显式地内生Python字符串,但是除非遇到这些特殊情况之一,否则两个相同的Python字符串文字将产生不同的字符串。

Python还可以自由地重用内存位置,并且Python还将通过在编译时将字节码与代码对象中的字节码一起存储一次来优化不可变文字。 Python REPL(交互式解释器)还将最新的表达式结果存储在名称'foo' * 20中,这使事情更加混乱。

因此,您会不时看到相同的ID。

仅运行REPL中的'foo' * 20行需要执行几个步骤:

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

'foo' * 20

这将显示已存储的常量以及已编译的字节码; 在这种情况下,字符串'foo' * 20和'foo' * 6单例。 由产生不可变值的简单表达式可以在此阶段进行优化,请参见下面有关优化器的说明。

在执行时,从代码常量加载字符串,然后'foo' * 20返回内存位置。 结果'foo' * 6值绑定到_,并打印:

'foo' * 20

该代码对象未得到任何引用,引用计数降至0,并且删除了该代码对象。 结果,字符串对象也是如此。

然后,如果重新运行相同的代码,Python可能会为新的字符串对象重用相同的内存位置。 如果重复此代码,通常会导致打印相同的内存地址。 这确实取决于您对Python内存的其他处理方式。

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

接下来,Python编译器还将实习存储为常量的任何Python字符串,只要它看起来足够像一个有效的标识符即可。 Python代码对象工厂函数PyCode_New将通过调用'foo' * 20来实习任何仅包含ASCII字母,数字或下划线的字符串对象。此函数遍历常量结构,并且对于在其中发现的任何字符串对象'foo' * 20执行:

if (all_name_chars(v)) {

PyObject *w = v;

PyUnicode_InternInPlace(&v);

if (w != v) {

PyTuple_SET_ITEM(tuple, i, v);

modified = 1;

}

}

其中'foo' * 20被记录为

/* all_name_chars(s): true iff s matches [a-zA-Z0-9_]* */

由于您创建的字符串符合该条件,因此它们会被扣留,这就是为什么您在第二个测试中看到'foo' * 20字符串使用相同的ID的原因:只要保留了对扣留版本的引用,则扣留将导致将来的'foo' * 6字面值 即使在新的代码块中并绑定到不同的标识符,也可以重用被嵌入的字符串对象。 在您的第一个测试中,您不会保存对字符串的引用,因此,在重新使用已插入的字符串之前,它们会被丢弃。

顺便说一句,您的新名称'foo' * 20将字符串绑定到包含相同字符的名称。 换句话说,您正在创建一个名称和值相等的全局变量。 随着Python既对标识符又对限定常量进行实习,您最终将对标识符及其值使用相同的字符串对象:

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

True

如果创建的字符串不是代码对象常量,或者包含字母+数字+下划线范围之外的字符,则会看到'foo' * 20值没有被重用:

>>> 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编译器使用窥孔优化器(Python版本<3.7)或更强大的AST优化器(3.7和更高版本)来预先计算(折叠)涉及常量的简单表达式的结果。 peepholder将其输出限制为长度为20或更短的序列(以防止膨胀的代码对象和内存使用),而AST优化器对4096个字符的字符串使用单独的限制。 这意味着如果结果字符串符合当前Python版本的优化程序限制,则仅由名称字符组成的较短字符串的串联仍可导致字符串被中断。

例如。 在Python 3.7上,'foo' * 20将产生一个单一的字符串,因为常量折叠会将其转换为单个值,而在Python 3.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

>>> 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值