在 David Beazley 的“Python Essential Reference”的第 35 页上,他首先指出:
对于字符串等不可变数据,解释器积极地在程序的不同部分之间共享对象。
然而,稍后在同一页上,他指出
对于数字和字符串等不可变对象,此分配有效地创建了一个副本。
但这不是矛盾吗?一方面他说它们是共享的,但他又说它们是复制的。
在 David Beazley 的“Python Essential Reference”的第 35 页上,他首先指出:
对于字符串等不可变数据,解释器积极地在程序的不同部分之间共享对象。
然而,稍后在同一页上,他指出
对于数字和字符串等不可变对象,此分配有效地创建了一个副本。
但这不是矛盾吗?一方面他说它们是共享的,但他又说它们是复制的。
python 中的赋值永远不会创建副本(从技术上讲,只有当类成员的赋值被重新定义时,例如通过使用__setattr__
、属性或描述符重新定义时才有可能)。
所以之后
a = foo()
b = a
从那里返回的任何内容foo
都没有被复制,而是您有两个变量a
并b
指向同一个对象。无论对象是否不可变。
但是,对于不可变对象,很难判断是否是这种情况(因为您不能使用一个变量来改变对象并使用另一个变量检查更改是否可见),因此您可以自由地认为确实a
并且b
不能相互影响。
对于一些不可变对象,Python 也可以自由地重用旧对象而不是创建新对象和之后
a = x + y
b = x + y
其中x
和y
都是数字(因此总和是数字并且是不可变的)可能两者都a
指向b
同一个对象。请注意,没有这样的保证......也可能是它们将指向具有相同值的不同对象。
要记住的重要一点是 Python 永远不会复制,除非特别指示使用 egcopy
或deepcopy
. 这对于可变对象非常重要,以避免意外。
例如,您可以看到一个常见的成语:
class Polygon:
def __init__(self, pts):
self.pts = pts[:]
...
在这种情况下self.pts = pts[:]
,使用而不是self.pts = pts
制作整个点数组的副本,以确保如果在创建对象后将更改应用于传递给构造函数的列表,点列表不会意外更改。
它有效地创建了一个副本。它实际上并没有创建副本。拥有两个副本和拥有两个名称共享相同值的主要区别在于,在后一种情况下,通过一个名称进行的修改会影响另一个名称的值。如果值不能被改变,这种差异就消失了,所以对于不可变对象,值是否被复制几乎没有实际影响。
id
在某些极端情况下,即使对于不可变类型(例如,通过使用函数或运算符),您也可以区分副本和不同对象之间的区别is
,但这些对于 Python 内置的不可变类型(如字符串和数字)没有用。
不,将预先存在的str
变量分配给新变量名称不会在内存中创建该值的独立副本。
可以使用该id()
函数检查内存中唯一对象的存在。例如,使用交互式 Python 提示符,尝试:
>>> str1 = 'ATG'
>>> str2 = str1
两者str1
和str2
具有相同的值:
>>> str1
'ATG'
>>> str2
'ATG'
这是因为str1
和str2
都指向同一个对象,这可以从它们共享相同的唯一对象 ID 的事实中得到证明:
>>> id(str1)
140439014052080
>>> id(str2)
140439014052080
>>> id(str1) == id(str2)
True
现在假设您修改str1
:
>>> str1 += 'TAG' # same as str1 = str1 + 'TAG'
>>> str1
ATGTAG
因为str
对象是不可变的,所以上面的赋值创建了一个具有自己 ID 的新的唯一对象:
>>> id(str1)
140439016777456
>>> id(str1) == id(str2)
False
但是,str2
保持与之前相同的 ID:
>>> id(str2)
140439014052080
因此,执行将具有自己唯一 ID 的str1 += 'TAG'
全新对象分配给变量,同时继续指向原始对象。str
str1
str2
str
这意味着将现有str
变量分配给另一个变量名称不会在内存中创建其值的副本。