在本章中,我们将讨论如何复制列表和嵌套列表的问题。我们将遇到的问题是可变数据类型的一般问题。尝试复制列表对于新手来说可能是一种难堪的体验。但在此之前,我们想总结一下上一章“数据类型和变量”的一些见解。在分配和复制简单数据类型(如整数和字符串)时,Python 甚至会为该语言的初学者展示一种奇怪的行为 - 与其他一些传统编程语言相比。浅拷贝和深拷贝之间的区别仅与复合对象有关,即包含其他对象的对象,如列表或类实例。

在下面的代码片段中,y 指向与 X 相同的内存位置。我们可以通过在 x 和 y 上应用 id() 函数来看到这一点。然而,与 C 和 C 中的“真实”指针不同,当我们为 y 分配一个新值时,情况会发生变化。在这种情况下, y 将收到一个单独的内存位置,正如我们在“数据类型和变量”一章中看到的,并且可以在以下示例中看到:

x = 3 y = x 打印( id ( x ), id ( y ))

输出:

94308023129184 94308023129184

y = 4 打印( id ( x ), id ( y ))

输出:

94308023129184 94308023129216

打印( x , y )

输出:

3 4

但是,即使与 C、C 、Perl 或 Java 等编程语言相比,这种内部行为看起来很奇怪,但前面赋值的可观察结果正是我们所期望的,即如果您不查看 id 值。但是,如果我们复制列表和字典等可变对象,则可能会出现问题。

Python 只创建真正的副本,如果它必须,即如果用户,程序员,明确要求它。

我们将向您介绍最关键的问题,这些问题在复制列表和字典等可变对象时可能会发生。

共享对象的变量

您在此页面上了解如何复制对象,尤其是列表。然而,你需要锻炼耐心。我们想向许多初学者展示一些看起来像副本但与副本无关的东西。

colours1 = [ "red" , "blue" ] colours2 = colours1 print ( colours1 , colours2 )

输出:

['红色','蓝色'] ['红色','蓝色']

两个变量都引用同一个列表对象。如果我们查看变量colours1and的身份colours2,我们可以看到两者都是对同一个对象的引用:

打印(id (颜色1 ), id (颜色2 ))

输出:

139720054914312 139720054914312

在上面的例子中,一个简单的列表被分配给 colours1。这个列表是所谓的“浅表”,因为它没有嵌套结构,即列表中不包含子列表。在下一步中,我们将 colours1 分配给 colours2。

id() 函数向我们展示了两个变量都指向同一个列表对象,即它们共享这个对象。

python 赋值深拷贝和浅拷贝的区别(python浅拷贝和深拷贝)(1)

我们有两个变量名colours1和colours2,我们将其描绘为黄色椭圆。蓝色框代表列表对象。列表对象由对其他对象的引用组成。在我们的示例中,由两个变量引用的列表对象引用了两个字符串对象,即“red”和“blue”。现在我们必须检查,如果我们只更改colours2or列表中的一个元素会发生什么colours1。

现在我们想看看,如果我们将一个新对象分配给 会发生什么colours2。正如预期的那样, 的值colours1保持不变。就像我们在“数据类型和变量”一章的例子中一样,已经为 分配了一个新的内存位置colours2,因为我们已经为这个变量分配了一个全新的列表,即一个新的列表对象。

colours1 = [ "red" , "blue" ] colours2 = colours1 print ( id ( colours1 ), id ( colours2 ))

输出:

139720055355208 139720055355208

colours2 = “绿色” 印花(colours1 )

输出:

['红蓝']

打印(颜色1 )

输出:

['红蓝']

打印(颜色2 )

输出:

绿色

我们为变量“colours2”分配了一个新对象。在下面的代码中,我们将通过为列表的第二个元素分配一个新值来在内部更改列表对象。

colours1 = [ "red" , "blue" ] colours2 = colours1 colours2 [ 1 ] = "green"

python 赋值深拷贝和浅拷贝的区别(python浅拷贝和深拷贝)(2)

让我们看看,在前面的代码中详细发生了什么。我们为colours2 的第二个元素分配了一个新值,即索引为1 的元素。许多初学者会感到惊讶,因为colors1 的列表也已“自动”更改。当然,我们没有两个列表:同一个列表只有两个名字!

解释是我们没有为变量分配一个新对象colours2。我们更改了colours2内部引用的对象,或者通常称为“就地”的对象。两个变量colours1和colours2仍然指向同一个列表对象。

复制列表

我们终于到了复制列表的话题。本list类提供了方法copy用于此目的。尽管名字似乎很明确,但这条路也有一个问题。

如果我们使用帮助,可以看到捕获copy:

帮助(列表。复制)

输出:

关于 method_descriptor 的帮助: 复制(自我,/) 返回列表的浅拷贝。

许多初学者在这里忽略了“浅”这个词。help告诉我们该copy方法将创建一个新列表。这个新列表将是原始列表的“浅”副本。

原则上,“浅”这个词在这个定义中是不必要的,甚至是误导性的。

首先,您应该记住 Python 中的列表是什么。Python 中的列表是一个对象,它由对 Python 对象的引用的有序序列组成。以下是字符串列表:

python 赋值深拷贝和浅拷贝的区别(python浅拷贝和深拷贝)(3)

变量firstnames引用的列表是一个字符串列表。基本上,列表对象只是带有箭头的蓝色框,即对字符串的引用。字符串本身不是列表的一部分。

名字 = [ '凯文' , '杰米娜' , '拉尔斯' , '玛丽亚' ]

前面的例子名字列表firstnames是同构的,即它只由字符串组成,所以所有元素都具有相同的数据类型。但是你应该知道一个列表的引用可以引用任何对象的事实。以下列表whatever是一个更一般的列表:

python 赋值深拷贝和浅拷贝的区别(python浅拷贝和深拷贝)(4)

无论如何 = [ "Kevin" , "Pythonista" , 7.8 , [ 3.54 , "rkm" ]]

复制列表时,我们复制引用。在我们的示例中,这些是由firstnames和引用的蓝色框whatever。其含义在以下子章节中显示。

复制列表的问题

复制列表很容易被误解,这种误解会导致令人不快的错误。我们将以稍微不同的爱情故事的形式呈现这一点。

python 赋值深拷贝和浅拷贝的区别(python浅拷贝和深拷贝)(5)

想象一个人住在康斯坦茨市。这座城市位于德国南部康斯坦茨湖畔(德语为博登湖)。发源于瑞士阿尔卑斯山的莱茵河穿过康斯坦茨湖并离开它,它的面积要大得多。这个叫 Swen 的人住在“Seestrasse”,这是康斯坦茨最昂贵的街道之一。他的公寓可以直接看到湖泊和康斯坦茨市。我们将一些关于 Swen 的信息放在以下列表结构中:

person1 = [ " Swen " , [ "Seestrasse" , "Konstanz" ]]

python 赋值深拷贝和浅拷贝的区别(python浅拷贝和深拷贝)(6)

在美好的一天,漂亮的莎拉遇到了她的白马王子斯温。简而言之:一见钟情,她搬进了斯温。

现在由我们为莎拉创建数据集。我们很懒,我们会回收Swen的数据,因为她现在和他住在同一个公寓里。

我们将复制 Swen 的数据并将名称更改为 Sarah:

人 2 = 人 1 。copy () person2 [ 0 ] = "Sarah" print ( person2 )

输出:

['莎拉',['Seestrasse','康斯坦茨']]

python 赋值深拷贝和浅拷贝的区别(python浅拷贝和深拷贝)(7)

他们生活在完美的和谐和深爱中很长一段时间,比方说整个周末。她突然意识到斯文是个怪物:他用果酱和蜂蜜弄脏了黄油,更糟糕的是,他把破旧的袜子摊在卧室里。没过多久,她就做出了一个关键的决定:她将离开他和梦想中的公寓。

她搬进了一条叫做 Bücklestrasse 的街道。一个远没有那么好,但至少她远离怪物的住宅区。

我们如何从 Python 的角度安排这一举措?

可以使用 访问该街道person2[1][0]。所以我们将其设置为新位置:

person2 [ 1 ][ 0 ] = "Bücklestraße"

我们可以看到 Sarah 成功移动到了新位置:

打印(person2 )

输出:

['莎拉',['Bücklestraße','康斯坦茨']]

我们的故事就这样结束了吗?她再也见不到斯文了吗?让我们检查Swen的数据:

打印( person1 )

输出:

['Swen', ['Bücklestraße', '康斯坦茨']]

斯文很粘人。她无法摆脱他!这对某些人来说可能相当令人惊讶。为什么会这样?该列表person1由两个引用:一种为字符串对象(“斯文”),而另一个嵌套列表(地址['Seestrasse', 'Konstanz']当我们使用。copy,我们复制仅这些参考文献,这意味着这两者。person1[1]和person2[1]引用相同的列表对象时。我们更改了这个嵌套列表,它在两个列表中都可见。

python 赋值深拷贝和浅拷贝的区别(python浅拷贝和深拷贝)(8)

来自模块副本的深层复制

模块提供了对所描述问题的解决方案copy。该模块提供了“deepcopy”方法,它允许对任意列表(即浅表和其他列表)进行完整或深层复制。

让我们用函数重做前面的例子deepcopy:

from copy import deepcopy person1 = [ " Swen " , [ "Seestrasse" , "Konstanz" ]] person2 = deepcopy ( person1 ) person2 [ 0 ] = "Sarah"

在此之后,实现结构如下所示:

python 赋值深拷贝和浅拷贝的区别(python浅拷贝和深拷贝)(9)

我们可以看到带有地址的嵌套列表也被复制了。我们现在已经为莎拉的逃跑般的举动做好了充分的准备。

person2 [ 1 ][ 0 ] = "Bücklestrasse"

她成功搬出并永远离开了斯文,如下所示:

打印(人1 ,人 2 )

输出:

['Swen', ['Seestrasse', 'Konstanz']] ['Sarah', ['Bücklestrasse', 'Konstanz']]

为了清楚起见,我们还在此处提供了一个图表。

python 赋值深拷贝和浅拷贝的区别(python浅拷贝和深拷贝)(10)

我们可以通过 id 函数看到子列表已经被复制了,因为id(person1[1])与id(person2[1]). 一个有趣的事实是字符串没有被复制:person1[1]并person2[1]引用相同的字符串。

打印( id ( person1 [ 1 ]), id ( person2 [ 1 ]))

输出:

139720051192456 139720054073864

,