【问题标题】:How can a non-assigned string in Python have an address in memory?Python中未分配的字符串如何在内存中具有地址?
【发布时间】:2011-08-03 19:07:20
【问题描述】:

谁能给我解释一下?所以我一直在玩python中的 id() 命令并遇到了这个:

>>> id('cat')
5181152
>>> a = 'cat'
>>> b = 'cat'
>>> id(a)
5181152
>>> id(b)
5181152

这对我来说很有意义,除了一个部分:字符串“cat”在我将它分配给变量之前在内存中有一个地址。我可能只是不明白内存寻址的工作原理,但有人可以向我解释一下,或者至少告诉我应该阅读内存寻址吗?

所以这一切都很好,但这让我更加困惑:

>>> a = a[0:2]+'t'
>>> a
'cat'
>>> id(a)
39964224
>>> id('cat')
5181152

这让我觉得很奇怪,因为 'cat' 是一个地址为 5181152 的字符串,但新的 a 有不同的地址。那么如果内存中有两个 'cat' 字符串,为什么不为 id('cat') 打印两个地址呢?我最后的想法是串联与地址的变化有关,所以我尝试了这个:

>>> id(b[0:2]+'t')
39921024
>>> b = b[0:2]+'t'
>>> b
'cat'
>>> id(b)
40000896

我会预测 ID 是相同的,但事实并非如此。想法?

【问题讨论】:

标签: python memory-address


【解决方案1】:

Python 相当积极地重用字符串文字。它这样做的规则是依赖于实现的,但是 CPython 使用了我知道的两个:

  • 仅包含在 Python 标识符中有效的字符的字符串是 interned, 这意味着它们存储在一个大表中,并在它们出现的任何地方重复使用。所以,无论你在哪里使用"cat",它总是指向同一个字符串对象。
  • 无论其内容和长度如何,都可以重用同一代码块中的字符串文字。如果将整个葛底斯堡地址的字符串文字放入一个函数中,两次,两次都是同一个字符串对象。在单独的函数中,它们是不同的对象: def foo(): return "pack my box with five dozen liquor jugs" def bar(): return "pack my box with five dozen liquor jugs" assert foo() is bar() # AssertionError

这两种优化都是在编译时(即生成字节码时)完成的。

另一方面,像chr(99) + chr(97) + chr(116) 这样的东西是一个字符串表达式,它的计算结果是字符串"cat"。在像 Python 这样的动态语言中,它的值在编译时是未知的(chr() 是一个内置函数,但您可能已经重新分配了它)所以它通常不会被实习。因此它的id() 不同于"cat"。但是,您可以使用 intern() 函数强制对字符串进行实习。因此:

id(intern(chr(99) + chr(97) + chr(116))) == id("cat")   # True

正如其他人所提到的,实习是可能的,因为字符串是不可变的。换句话说,无法将"cat" 更改为"dog"。您必须生成一个新的字符串对象,这意味着指向同一字符串的其他名称不会受到影响。

顺便说一句,Python 还在编译时将仅包含常量的表达式(如"c" + "a" + "t")转换为常量,如下面的反汇编所示。这些将根据上述规则进行优化以指向相同的字符串对象。

>>> def foo(): "c" + "a" + "t"
...
>>> from dis import dis; dis(foo)
  1           0 LOAD_CONST               5 ('cat')
              3 POP_TOP
              4 LOAD_CONST               0 (None)
              7 RETURN_VALUE

【讨论】:

【解决方案2】:

'cat' 有一个地址,因为您创建它是为了将其传递给id()。您尚未将其绑定到名称,但该对象仍然存在。

Python 缓存和重用短字符串。但是如果你通过连接来组装字符串,那么搜索缓存并尝试重用的代码就会被绕过。

请注意,字符串缓存的内部工作是纯粹的实现细节,不应依赖。

【讨论】:

    【解决方案3】:

    所有值都必须驻留在内存中的某个位置。这就是id('cat') 产生值的原因。你称它为“不存在”的字符串,但它显然确实存在,只是还没有被分配一个名称。

    字符串是不可变的,因此解释器可以做一些巧妙的事情,例如使文字 'cat' 的所有实例成为同一个对象,从而使 id(a)id(b) 相同。

    对字符串进行操作会产生新的字符串。这些可能是也可能不是与以前具有相同内容的字符串相同的字符串。

    请注意,所有这些细节都是 CPython 的实现细节,它们可以随时更改。在实际程序中,您无需关心这些问题。

    【讨论】:

      【解决方案4】:

      Python 变量与其他语言(例如 C)中的变量完全不同。

      在许多其他语言中,变量是内存中某个位置的名称。在这些语言中,不同种类的变量可以指代不同种类的位置,而同一个位置可以有多个名称。在大多数情况下,给定的内存位置可能会不时更改数据。还有一些方法可以间接引用内存位置(int *p 将包含地址,并且在该地址的内存位置中,有一个整数。)但是变量引用的实际位置不能改变;变量的位置。这些语言中的变量赋值实际上是“查找此变量的位置,并将此数据复制到该位置”

      Python 不是这样工作的。在 python 中,实际对象位于某个内存位置,变量就像位置的标签。 Python 管理存储值的方式与管理变量的方式不同。本质上,python 中的赋值意味着“查找此变量的信息,忘记它已经引用的位置,并用这个新位置替换它”。没有数据被复制。

      像 python 一样工作的语言的一个共同特征(与我们之前讨论的第一种不同)是某些类型的对象以一种特殊的方式进行管理;相同的值被缓存,这样它们就不会占用额外的内存,并且可以很容易地比较它们(如果它们具有相同的地址,它们是相等的)。这个过程称为interning;所有 python 字符串文字都被实习(除了一些其他类型),尽管动态创建的字符串可能不是。

      在您的确切代码中,语义对话框将是:

      # before anything, since 'cat' is a literal constant, add it to the intern cache
      >>> id('cat') # grab the constant 'cat' from the intern cache and look up 
                    # it's address
      5181152
      >>> a = 'cat' # grab the constant 'cat' from the intern cache and 
                    # make the variable "a" point to it's location 
      >>> b = 'cat' # do the same thing with the variable "b"
      >>> id(a) # look up the object "a" currently points to, 
                # then look up that object's address
      5181152
      >>> id(b) # look up the object "b" currently points to, 
                # then look up that object's address
      5181152
      

      【讨论】:

        【解决方案5】:

        您发布的代码创建了新字符串作为中间对象。这些创建的字符串最终与您的原始字符串具有相同的内容。在中间时间段内,它们与原始的不完全匹配,必须保存在不同的地址。

        >>> id('cat')
        5181152
        

        正如其他人所回答的那样,通过发出这些指令,您可以使 Python VM 创建一个包含字符串“cat”的字符串对象。此字符串对象已缓存,地址为 5181152。

        >>> a = 'cat'
        >>> id(a)
        5181152
        

        同样,a 已被分配以引用此缓存的字符串对象 5181152,其中包含“cat”。

        >>> a = a[0:2]
        >>> id(a)
        27731511
        

        此时,在您的程序的修改版本中,您创建了两个小字符串对象:'cat''ca''cat' 仍然存在于缓存中。 a 所指的字符串是一个不同的并且可能是新颖的字符串对象,包含字符'ca'

        >>> a = a + 't'
        >>> id(a)
        39964224
        

        现在您已经创建了另一个新的字符串对象。该对象是地址 27731511 处的字符串 'ca' 和字符串 't' 的串联。此连接确实与先前缓存的字符串 'cat' 匹配。 Python 不会自动检测这种情况。如前所述,您可以使用intern() 方法强制搜索。

        希望这个解释能够阐明a 地址更改的步骤。

        您的代码不包含a 分配字符串'ca' 的中间状态。答案仍然适用,因为 Python 解释器会生成一个新的字符串对象来保存中间结果 a[0:2],无论您是否将该中间结果分配给变量。

        【讨论】:

          猜你喜欢
          • 2015-07-01
          • 2017-01-18
          • 1970-01-01
          • 1970-01-01
          • 2023-03-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多