关于不可变字符串的改变 id

2024-12-16 08:35:00
admin
原创
162
摘要:问题描述:id关于类型的对象的str(在 Python 2.7 中)的一些东西让我感到困惑。该str类型是不可变的,所以我期望一旦创建它,​​它将始终具有相同的id。我认为我的表达能力不是很好,所以我将发布一个输入和输出序列的示例。>>> id('so') 140614155123888 &...

问题描述:

id关于类型的对象的str(在 Python 2.7 中)的一些东西让我感到困惑。该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

現在它平等的...


解决方案 1:

CPython 并不承诺默认驻留所有字符串,但实际上,Python 代码库中的很多地方都会重用已创建的字符串对象。许多 Python 内部使用(相当于 C 的)sys.intern()函数调用来显式驻留 Python 字符串,但除非遇到这些特殊情况之一,否则两个相同的 Python 字符串文字将产生不同的字符串。

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

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

id(<string literal>)在 REPL 中运行该行需要经过几个步骤:

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

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

这显示了已编译字节码的存储常量;在本例中为字符串'foo'None单例。由产生不可变值的简单表达式组成的表达式可在此阶段进行优化,请参阅下面有关优化器的说明。

  1. 执行时,字符串从代码常量中加载,并id()返回内存位置。结果int值绑定到_,并打印:

>>> 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        
  1. 代码对象不再被任何对象引用,引用计数降至 0,代码对象被删除。同样,字符串对象也被删除。

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

ID 重用是不可预测的;如果与此同时垃圾收集器运行以清除循环引用,则其他内存可能会被释放,您将获得新的内存地址。

接下来,Python 编译器还将驻留任何作为常量存储的 Python 字符串,前提是它看起来足够像一个有效的标识符。Python代码对象工厂函数 PyCode_New将通过调用驻留任何仅包含 ASCII 字母、数字或下划线的字符串对象intern_string_constants()。此函数递归遍历常量结构,并对v在其中找到的任何字符串对象执行:

if (all_name_chars(v)) {
    PyObject *w = v;
    PyUnicode_InternInPlace(&v);
    if (w != v) {
        PyTuple_SET_ITEM(tuple, i, v);
        modified = 1;
    }
}

其中all_name_chars()记录为

/* all_name_chars(s): true iff s matches [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

如果您创建的字符串不是代码对象常量,或者包含字母+数字+下划线范围之外的字符,您将看到该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 编译器要么使用窥孔优化器(Python 版本 < 3.7),要么使用功能更强大的AST 优化器(3.7 及更新版本)来预先计算(折叠)涉及常量的简单表达式的结果。窥孔优化器将其输出限制为长度为 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

解决方案 2:

此行为特定于 Python 交互式 shell。如果我将以下内容放入 .py 文件中:

print id('so')
print id('so')
print id('so')

并执行它,我收到以下输出:

2888960
2888960
2888960

在 CPython 中,字符串文字被视为常量,我们可以在上面代码片段的字节码中看到:

  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相同。

解决方案 3:

'so'在您的第一个例子中,每次都会创建一个新的字符串实例,因此 id 不同。

在第二个示例中,您将字符串绑定到变量,然后 Python 可以维护该字符串的共享副本。

解决方案 4:

理解行为的更简单的方法是检查以下数据类型和变量。

“字符串的特殊性”部分使用特殊字符作为示例来说明您的问题。

解决方案 5:

因此,尽管 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
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1579  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1355  
  信创产品在政府采购中的占比分析随着信息技术的飞速发展以及国家对信息安全重视程度的不断提高,信创产业应运而生并迅速崛起。信创,即信息技术应用创新,旨在实现信息技术领域的自主可控,减少对国外技术的依赖,保障国家信息安全。政府采购作为推动信创产业发展的重要力量,其对信创产品的采购占比情况备受关注。这不仅关系到信创产业的发展前...
信创和国产化的区别   8  
  信创,即信息技术应用创新产业,旨在实现信息技术领域的自主可控,摆脱对国外技术的依赖。近年来,国货国用信创发展势头迅猛,在诸多领域取得了显著成果。这一发展趋势对科技创新产生了深远的推动作用,不仅提升了我国在信息技术领域的自主创新能力,还为经济社会的数字化转型提供了坚实支撑。信创推动核心技术突破信创产业的发展促使企业和科研...
信创工作   9  
  信创技术,即信息技术应用创新产业,旨在实现信息技术领域的自主可控与安全可靠。近年来,信创技术发展迅猛,对中小企业产生了深远的影响,带来了诸多不可忽视的价值。在数字化转型的浪潮中,中小企业面临着激烈的市场竞争和复杂多变的环境,信创技术的出现为它们提供了新的发展机遇和支撑。信创技术对中小企业的影响技术架构变革信创技术促使中...
信创国产化   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用