Python中的非赋值string如何在内存中有一个地址?
谁可以给我解释一下这个? 所以我一直在使用Python中的id()命令,碰到这个:
>>> id('cat') 5181152 >>> a = 'cat' >>> b = 'cat' >>> id(a) 5181152 >>> id(b) 5181152
除了一个部分,这对我来说是有意义的:string'cat'在我将其分配给一个variables之前在内存中有一个地址。 我可能只是不明白如何内存寻址的作品,但有人可以解释这个给我,或者至less告诉我,我应该阅读内存寻址?
所以这一切都很好,但这使我更加困惑:
>>> a = a[0:2]+'t' >>> a 'cat' >>> id(a) 39964224 >>> id('cat') 5181152
这让我感到奇怪,因为'猫'是一个地址为5181152的string,但是新的地址是不同的。 所以,如果在内存中有两个“猫”string,为什么不为id('cat')打印两个地址? 我最后的想法是,连接与地址的变化有关,所以我尝试了这个:
>>> id(b[0:2]+'t') 39921024 >>> b = b[0:2]+'t' >>> b 'cat' >>> id(b) 40000896
我会预测的ID是相同的,但事实并非如此。 思考?
Python相当积极地重用string文字。 它所遵循的规则是依赖于实现的,但是CPython使用了我所知道的两个:
- 只包含Python标识符中有效字符的string被实现,这意味着它们被存储在一个大表中,并在任何地方重用。 所以,无论你在哪里使用
"cat"
,它总是指的是相同的string对象。 - 不pipe其内容和长度如何,相同代码块中的string文字都被重用。 如果你把整个葛底斯堡地址的string文字放在一个函数中,两次,这两次都是同一个string对象。 在不同的函数中,它们是不同的对象:
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)
是一个stringexpression式 ,其值为string"cat"
。 在像Python这样的dynamic语言中,它的值在编译时是不能被知道的( chr()
是一个内置的函数,但是你可能已经重新分配了它),所以它通常不被实现。 因此它的id()
与"cat"
。 但是,可以使用intern()
函数强制string被执行。 从而:
id(intern(chr(99) + chr(97) + chr(116))) == id("cat") # True
正如其他人所说,实习是可能的,因为string是不可改变的。 换句话说,不可能把"cat"
换成"dog"
。 您必须生成一个新的string对象,这意味着指向同一string的其他名称不会受到影响。
就像在旁边一样,Python也会在编译时将包含常量的expression式(如"c" + "a" + "t"
)转换为常量,如下面的反汇编所示。 根据上述规则,这些将被优化以指向相同的string对象。
>>> 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
'cat'
有一个地址,因为你创build它来传递给id()
。 你还没有绑定到一个名字,但是这个对象还是存在的。
Pythoncaching并重用短string。 但是,如果通过串联组装string,则会绕过searchcaching并尝试重新使用的代码。
请注意,stringcaching的内部工作是纯粹的实现细节,不应该依赖。
所有值必须驻留在内存中的某个位置。 这就是为什么id('cat')
产生一个值。 你把它称为“不存在的”string,但它显然确实存在,它还没有被分配到一个名字。
string是不可变的,所以解释器可以做一些聪明的事情,比如使string'cat'
所有实例成为同一个对象,这样id(a)
和id(b)
是相同的。
在string上操作会产生新的string。 这些string可能与以前的具有相同内容的string相同或不同。
请注意,所有这些细节都是CPython的实现细节,并且可以随时更改。 在实际的程序中,你不需要关心这些问题。
Pythonvariables与其他语言中的variables(比如说C)不同。
在许多其他语言中,variables是内存中位置的名称。 在这些语言中,不同types的variables可以指不同types的位置,并且相同的位置可以被赋予多个名称。 大多数情况下,给定的内存位置可能会使数据不时变化。 也有方法间接引用内存位置( int *p
将包含地址,并且在该地址的内存位置,有一个整数)。但是variables引用的实际位置不能改变; variables是位置。 这些语言中的variables赋值有效地是“查找该variables的位置,并将该数据复制到该位置”
Python不能这样工作。 在Python中,实际的对象进入一些内存位置,variables就像位置标签。 Python以与pipe理variables的方式不同的方式pipe理存储的值。 实际上,在python中的一个赋值意味着“查找这个variables的信息,忘记它已经引用的位置,并用这个新位置replace它”。 没有数据被复制。
Python的一个常见function就像python(与我们之前讨论的第一种方法相反)是以某种特殊的方式pipe理某些对象, 相同的值被caching,所以它们不占用额外的内存,所以它们可以很容易地进行比较(如果它们具有相同的地址,它们是相等的)。 这个过程被称为实习 。 所有的pythonstring都是被实现的(除了一些其他types),尽pipedynamic创build的string可能不是。
在您的确切代码中,语义对话框将是:
# 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
您发布的代码将创build新string作为中间对象。 这些创build的string最终与您的原件具有相同的内容。 在中间时期,它们与原文不完全一致,必须保存在不同的地址。
>>> id('cat') 5181152
正如其他人所回答的那样,通过发出这些指令,您可以使Python VM创build一个包含string“cat”的string对象。 该string对象被caching,并在地址5181152。
>>> a = 'cat' >>> id(a) 5181152
同样,a已被分配来引用5181152caching的string对象,其中包含“cat”。
>>> a = a[0:2] >>> id(a) 27731511
在我的程序的修改版本中,您已经创build了两个小string对象: 'cat'
和'ca'
。 'cat'
仍然存在于caching中。 a
引用的string是一个不同的,可能是新颖的string对象,包含字符'ca'
。
>>> a = a + 't' >>> id(a) 39964224
现在你已经创build了另一个新的string对象 该对象是地址为27731511的string'ca'
和string't'
的串联。 这个连接匹配以前caching的string'cat'
。 Python不会自动检测到这种情况。 如kindall所示,你可以用intern()
方法强制search。
希望这个解释说明改变地址的步骤。
您的代码不包含赋予string'ca'
的中间状态。 答案仍然适用,因为Python解释器确实会生成一个新的string对象来保存中间结果a[0:2]
,而不pipe是否将该中间结果赋值给一个variables。