gpt4 book ai didi

python - 关于不可变字符串的变化id

转载 作者:IT老高 更新时间:2023-10-28 21:39:56 27 4
gpt4 key购买 nike

关于 id类型的对象 str (在python 2.7中)让我感到困惑。 str type 是不可变的,所以我希望一旦它被创建,它就会始终具有相同的 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 sodel not_soid('so')的输出再次开始改变。

这是 不是 与(小)整数相同的行为。

我知道不变性和具有相同的 id 之间没有真正的联系;尽管如此,我仍在试图找出这种行为的根源。我相信熟悉 python 内部结构的人不会比我感到惊讶,所以我试图达到同样的目的......

更新

用不同的字符串尝试相同的结果会产生不同的结果......
>>> id('hello')
139978087896384
>>> id('hello')
139978087896384
>>> id('hello')
139978087896384

现在它 平等的...

最佳答案

默认情况下,CPython 不 promise 将所有字符串都实习,但在实践中,Python 代码库中的很多地方都会重用已创建的字符串对象。许多 Python 内部结构使用(C 等效项) sys.intern() function call 来显式插入 Python 字符串,但除非您遇到这些特殊情况之一,否则两个相同的 Python 字符串文字将产生不同的字符串。

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

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

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

  • 该行被编译,其中包括为字符串对象创建一个常量:
    >>> compile("id('foo')", '<stdin>', 'single').co_consts
    ('foo', None)

    这显示了带有编译字节码的存储常量;在这种情况下,一个字符串 'foo'None 单例。在此阶段可以优化由产生不可变值的简单表达式组成,请参阅下面关于优化器的说明。
  • 执行时,从代码常量加载字符串,id() 返回内存位置。生成的 int 值绑定(bind)到 _ ,并打印:
    >>> 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 object factory function PyCode_New 将通过调用 intern_string_constants() 来实习任何只包含 ASCII 字母、数字或下划线的字符串对象。该函数通过常量结构递归,并且对于在那里找到的任何字符串对象 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_]* */

    由于您创建了符合该标准的字符串,因此它们被实习,这就是为什么您会在第二次测试中看到用于 'so' 字符串的相同 ID:只要对实习版本的引用仍然存在,实习将导致 future 的 'so' 文字重用实习字符串对象,即使在新代码块中并绑定(bind)到不同的标识符。在您的第一个测试中,您没有保存对字符串的引用,因此在可以重用之前丢弃了内部字符串。

    顺便说一下,您的新名称 so = 'so' 将字符串绑定(bind)到包含相同字符的名称。换句话说,您正在创建一个名称和值相等的全局变量。当 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 编译器使用 peephole optimizer(Python 版本 < 3.7)或功能更强大的 AST optimizer(3.7 和更高版本)来预先计算(折叠)涉及常量的简单表达式的结果。 peepholder 将其输出限制为长度不超过 20 的序列(以防止代码对象膨胀和内存使用),而 AST 优化器对 4096 个字符的字符串使用单独的限制。这意味着,如果生成的字符串符合当前 Python 版本的优化器限制,则连接仅由名称字符组成的较短字符串仍会导致内部字符串。

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

    关于python - 关于不可变字符串的变化id,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24245324/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com