我相信了解正在发生的事情的最简单方法是使用视觉表示(这种表示的想法不是我的,尽管我喜欢它)。
首先,您必须了解在 python 中只有对对象的引用。对象本身彼此分开生活。例如,列表[0, 1]
是一个列表对象,其中包含对 object和 object的引用。参考是某种链接。这与其他语言中的变量不同,因为变量通常是您放置东西的内存位置。在 python 中,“变量”,即标识符,只是对象的“名称”(=reference)。0
1
为了理解这一点,让我们用一个比喻来描绘物体之间的关系:假设物体是海中的重石,它们通过绳索和钩子(¿)连接在一起。在海面上生活着指代物体的标识符。标识符是防止物体沉入深处的浮标(他们说,海怪(又名垃圾收集器)会摧毁它们)。
例如,我们可以表示这种情况:
a = [0, 1]
用下图:
___
( )
~~~~~~~~( a )~~~~~~~~
(___)
o ¿ o
| O
| o
|
|
+------+-------+
| [ ¿ , ¿ ] |
+----|-----|---+
| |
| |
o | |
O | |
| |
+-+-+ +-+-+
| 0 | | 1 |
+---+ +---+
o O o
)
( ) o
) )( ) ( (
( ( )( ( ( ) )
如您所见,标识符a
指的是,即用绳索链接到列表对象。列表对象有两个插槽,每个插槽都包含一个连接到对象的链接0
和1
.
现在,如果我们这样做:
b = a
标识符b
将引用相同的对象a
:
___ ___
( ) ( )
~~~~~~~~~~~( a )~~~~~~~~~~~~~~~( b )~~~~~~~~~~~~~~~~
(___) (___)
¿ ¿
\ /
o \ / o
o \ / o
-------+-------
O | [ ¿ , ¿ ] | O
----|-----|----
| |
+-+-+ +-+-+
o | 0 | | 1 |
+---+ +---+ o
O
o O
o
)
) ( ) (
( ( )( ( ( )
( ) ) ( ) ( ( ) ) ( )
相反,当您通过以下方式对, 进行浅表复制a
时:
b = a[:]
创建了一个新列表,其元素是对引用的对象的引用的副本,即您制作了绳索的副本,但它们指向相同的元素:a
___ ___
( ) ( )
~~~~~~~~~~~( a )~~~~~~~~~~~~~~~( b )~~~~~~~~~~~~~~~~
(___) (___)
O ¿ ¿ o
| |
o | |
| |
-------+------ ------+-------
| [ ¿ , ¿ ] | | [ ¿ , ¿ ] |
----|----|---- ----|----|----
| | | |
\ \ / /
\ \ / /
\ \ / / o
o \ \ / / o
\ \ / / o
o \ \ / /
\ \ / / o
O \ X /
\ / \ /
\/ \/
| |
| |
| |
+-+-+ +-+-+
| 0 | | 1 |
+---+ +---+
)
( ( ) (
)( ) ) ) ( ( ) ) )
( ) ( ) ( ( ( ( ) ) ( ) ( ( (
由于整数是不可变的,因此使用副本或相同的对象之间没有任何区别,但是当您用可变list
的 s替换整数时,您最终会修改对同一对象的引用,因此您会看到行为。
从视觉上看,代码:
a = [[0, 1], [0, 1]]
b = a[:]
结果是:
___ ___
( ) ( )
~~~~~~~~~~~( a )~~~~~~~~~~~~~~~( b )~~~~~~~~~~~~~~~~
(___) (___)
O ¿ ¿ o
| |
o | |
| |
-------+------ ------+-------
| [ ¿ , ¿ ] | | [ ¿ , ¿ ] |
----|----|---- ----|----|----
| \ / |
| \ / |
| \ / |
| \ / |
| \ / |
| \ / |
| \ / |
| X |
| / \ |
| / \ |
| / \ |
| / \ |
| / \ |
| / \ |
| | \ |
| | | |
+----+-----+----+ +-----+----+----+
| [ ¿ , ¿ ] | | [ ¿ , ¿ ] |
+----|-----|----+ +----|-----|----+
\ \ / /
\ \ / /
\ \ / /
\ \ / /
\ \ / /
\ | / /
| |/ /
| X /
| / | /
| / | /
\ / \ /
Y Y
| |
+-+-+ +-+-+
| 0 | | 1 |
+---+ +---+
)
( ( ) (
)( ) ) ) ( ( ) ) )
( ) ( ) ( ( ( ( ) ) ( ) ( ( (
请注意该列表如何b
引用 的相同子列表a
。(实现细节:CPython 的字节码编译器会优化文字表达式,以便在两个子列表中使用相同的0
和1
对象。对于小整数还涉及一些缓存,但这并不重要。在一般情况下,子列表没有全部共同元素)。
深层副本是避免共享相同对象的副本。
例如,执行后:
import copy
a = [[0, 1], [0, 1]]
b = copy.deepcopy(a)
情况是:
___ ___
( ) ( )
~~~~~~~~~~~( a )~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~( b )~~~~~~~~~~~~~~~~
(___) (___)
O ¿ ¿ o
| |
o | |
| |
-------+------ -------+------
| [ ¿ , ¿ ] | | [ ¿ , ¿ ] |
----|----|---- ----|----|----
| \ | \
| \ | \
| \ | \
| \ | \
| \ | \
| \ | \
| \ | \
| \ | \
| \ | \
| \ | \
| \ | \
| \ | \
+----+----------+ +--+------------+ +----+----------+ +--+------------+
| [ ¿ , ¿ ] | | [ ¿ , ¿ ] | | [ ¿ , ¿ ] | | [ ¿ , ¿ ] |
+----|-----|----+ +----|-----|----+ +----|-----|----+ +----|-----|----+
\ \ / / \ \ / /
\ \ / / \ \ / /
\ \ / / \ \ / /
\ \ / / \ \ / /
\ \ / / \ \ / /
\ | / / \ | / /
| |/ / | |/ /
| X / | X /
| / | / | / | /
| / | / | / | /
\ / \ / \ / \ /
Y Y Y Y
| | | |
+-+-+ +-+-+ +-+-+ +-+-+
| 0 | | 1 | | 0 | | 1 |
+---+ +---+ +---+ +---+
) )
( ( ) ( ( ( ) (
)( ) ) ) ( ( ) ) ) )( ) ) ) ( ( ) ) )
( ) ( ) ( ( ( ( ) ) ( ) ( ( ( ( ) ( ) ( ( ( ( ) ) ( ) ( ( (
(实际上,它似乎copy.deepcopy
足够聪明,可以避免复制不可变的内置对象,例如不可变对象的int
, long
, tuple
s 等,因此所有子列表共享相同的0
对象1
)
请注意,这些图表还可以帮助您了解引用计数的工作原理。每条绳索都是一个引用,直到一个对象有一个引用链,该引用链上升到一个浮标(即标识符),它才会保持活动状态。当没有更多的绳索将物体连接到表面的浮标时,物体就会下沉,并被垃圾收集器销毁。