一、随笔
从没想过浅拷贝和引用是不是一回事,以前没仔细研究过,有些模糊,今天才发现是对新手和门外汉(我特么就是个门外汉)的低砍腿(切踢、下路扫腿、扫堂腿,低扫腿,扫下盘)。姜维打击!!!
二、程序场景
>>> results = []
>>> path = [1,2,3]
>>> results.append(path)
>>> results
[[1, 2, 3]]
>>> results = []
>>> path = [1,2,3]
>>> results.apppend(path[:])
>>> results
[[1, 2, 3]]
上面这两个,目前答案是一样的,但是后续path变化,效果就不同了
>>> results = []
>>> path = [1,2,3]
>>> results.append(path)
>>> results
[[1, 2, 3]]
>>> results.append(path[:])
>>> results
[[1, 2, 3], [1, 2, 3]]
>>> path.append(4)
>>> results
[[1, 2, 3, 4], [1, 2, 3]]
- 特别注意:path增加到了4,但是resutls中只有第一个元素变化了。
在Python中,results.append(path)
和results.append(path[:])
的主要区别在于它们如何处理列表的引用。
results.append(path)
:这种方式将path
列表的引用添加到results
列表中。这意味着,如果之后path
列表的内容发生变化,那么results
中的相应元素也会反映出这些变化,因为它们引用的是同一个列表对象。results.append(path[:])
:这种方式首先创建path
列表的一个浅拷贝,然后将这个拷贝的引用添加到results
列表中。因此,即使之后path
列表的内容发生变化,results
中的相应元素也不会受到影响,因为它们引用的是path
列表的旧拷贝。
简单来说,使用results.append(path[:])
可以确保results
中的每个元素都是独立的,不会受到原始path
列表后续变化的影响。而results.append(path)
则会导致results
中的元素随着path
的变化而变化。
三、列表展示
下面是一个详细的列表,展示了在Python中,对于不同数据类型(数字、字符串、元组、字典、列表、集合、对象)使用引用、浅拷贝和深拷贝时的行为详情:
数据类型 | 引用 (Copy by Reference) | 浅拷贝 (Shallow Copy) | 深拷贝 (Deep Copy) |
---|---|---|---|
数字 (int, float) | 创建一个指向同一内存位置的引用。修改原始值不会影响引用。 | 创建一个新值,与原始值相同。 | 创建一个新值,与原始值相同。 |
字符串 (str) | 创建一个指向同一内存位置的引用。由于字符串是不可变的,修改原始字符串不会影响引用。 | 创建一个新的字符串,与原始字符串相同。 | 创建一个新的字符串,与原始字符串相同。 |
元组 (tuple) | 创建一个指向同一内存位置的引用。由于元组是不可变的,修改原始元组不会影响引用。 | 创建一个新的元组,与原始元组相同。 | 创建一个新的元组,与原始元组相同。 |
字典 (dict) | 创建一个指向同一字典的引用。修改原始字典会影响引用。 | 创建一个新的字典,但保留原始字典中元素的引用。 | 创建一个新的字典,并递归复制所有元素。 |
列表 (list) | 创建一个指向同一列表的引用。修改原始列表会影响引用。 | 创建一个新的列表,但保留原始列表中元素的引用。 | 创建一个新的列表,并递归复制所有元素。 |
集合 (set) | 创建一个指向同一集合的引用。修改原始集合会影响引用。 | 创建一个新的集合,但保留原始集合中元素的引用。 | 创建一个新的集合,并递归复制所有元素。 |
对象 (object) | 创建一个指向同一对象的引用。修改原始对象会影响引用。 | 创建一个新的对象,但保留对象内部属性的引用。 | 创建一个新的对象,并递归复制所有属性。 |
注意事项:
- 对于不可变数据类型(如数字、字符串、元组),引用和浅拷贝的效果是相同的,因为它们的值不能被改变。
- 深拷贝会递归复制所有可变数据类型,以确保原始对象和复制对象之间完全独立。
- 对于自定义对象,深拷贝会尝试复制对象的所有属性,这可能涉及到对象的
__deepcopy__
方法。 - 浅拷贝和深拷贝对于嵌套数据结构(如列表中的列表)的影响最为明显,因为浅拷贝只复制最外层的容器,而深拷贝会复制所有层级。
在Python中,可以使用copy
模块来进行浅拷贝和深拷贝:
import copy
original_list = [[1, 2, 3], [4, 5, 6]]
# 浅拷贝
shallow_copied_list = copy.copy(original_list)
# 深拷贝
deep_copied_list = copy.deepcopy(original_list)
在这个例子中,如果你修改了original_list
中的任何内部列表,这些修改会在shallow_copied_list
中体现出来,但不会在deep_copied_list
中体现。
四、扩展
在Python中,is
和==
操作符用于比较对象,但它们的目的和结果不同。is
用于比较两个对象的身份(即它们是否是同一个对象),而==
用于比较两个对象的值(即它们是否具有相同的值)。对于引用、浅拷贝和深拷贝,这两个操作符的作用会有所不同。下面是一个列表,展示了对于不同数据类型(包括None
)使用is
和==
时的行为详情:
数据类型/操作 | 引用 (Copy by Reference) | 浅拷贝 (Shallow Copy) | 深拷贝 (Deep Copy) |
---|---|---|---|
数字 (int, float) | is :True(对于不可变类型,引用和浅拷贝通常指向同一个对象)== :True | is :False(创建了新的对象)== :True | is :False(创建了新的对象)== :True |
字符串 (str) | is :True(对于不可变类型,引用和浅拷贝通常指向同一个对象)== :True | is :False(创建了新的对象)== :True | is :False(创建了新的对象)== :True |
元组 (tuple) | is :True(对于不可变类型,引用和浅拷贝通常指向同一个对象)== :True | is :False(创建了新的对象)== :True | is :False(创建了新的对象)== :True |
字典 (dict) | is :True(指向同一个对象)== :True | is :False(创建了新的字典,但内部元素是引用)== :True | is :False(创建了新的字典,所有元素都被复制)== :True |
列表 (list) | is :True(指向同一个对象)== :True | is :False(创建了新的列表,但内部元素是引用)== :True | is :False(创建了新的列表,所有元素都被复制)== :True |
集合 (set) | is :True(指向同一个对象)== :True | is :False(创建了新的集合,但内部元素是引用)== :True | is :False(创建了新的集合,所有元素都被复制)== :True |
对象 (object) | is :True(指向同一个对象)== :取决于对象的__eq__ 方法 | is :False(创建了新的对象,但内部属性是引用)== :取决于对象的__eq__ 方法 | is :False(创建了新的对象,所有属性都被复制)== :取决于对象的__eq__ 方法 |
None | is :True(只有None 是自身的引用)== :True | is :False(None 是不可变的,但浅拷贝会创建新的对象)== :True | is :False(None 是不可变的,但深拷贝会创建新的对象)== :True |
注意事项: |
- 对于不可变数据类型(如数字、字符串、元组),
is
和==
在引用和浅拷贝的情况下通常都返回True
,因为Python可能会优化这些不可变类型的重复值,使它们共享相同的内存地址。 - 对于可变数据类型(如字典、列表、集合),
is
在引用时返回True
,在浅拷贝和深拷贝时返回False
,因为这两种拷贝方式都会创建新的对象。 - 对于自定义对象,
==
的行为取决于对象的__eq__
方法。 None
是一个特殊的单例对象,只有None
是自身的引用,所以is None
和== None
通常用于检查是否为None
。
在Python中,is
和==
的正确使用取决于你想要比较的是对象的身份还是值。通常,当你想要比较两个对象是否完全相同(即它们是否是同一个对象)时,你应该使用is
。当你想要比较两个对象的值是否相等时,你应该使用==
。
在Python中,is
、==
和=
用于不同的目的,它们在判断引用、浅拷贝和深拷贝时的作用也不同。下面是一个列表,展示了这些操作符对None
和其他所有对象数据类型的作用:
操作符 | 描述 | 对None的作用 | 对其他对象数据类型的作用 |
---|---|---|---|
is | 比较两个对象的身份(即是否为同一个对象的实例)。 | 用于检查是否为None ,例如obj is None 。 | 用于检查两个变量是否引用同一个对象。 |
== | 比较两个对象的值是否相等。 | 不用于检查None ,因为None == None 是True,但这不提供额外信息。 | 用于检查两个对象的值是否相等。 |
= | 赋值操作符,用于将右侧的值赋给左侧的变量。 | 用于将None 赋值给变量,例如obj = None 。 | 用于将对象的值或引用赋值给变量。 |
对于引用、浅拷贝和深拷贝,这些操作符的作用如下:
操作符 | 引用 | 浅拷贝 | 深拷贝 |
---|---|---|---|
is | 用于检查两个变量是否引用同一个对象。 | is 用于检查浅拷贝后的对象是否与原始对象相同(对于不可变类型,它们通常是相同的;对于可变类型,它们是不同的)。 | is 用于检查深拷贝后的对象是否与原始对象相同(它们通常是不同的)。 |
== | 用于检查两个引用的对象是否具有相同的值。 | == 用于检查浅拷贝后的对象是否具有与原始对象相同的值(它们通常是相同的,但如果原始对象包含可变对象,则可能会不同)。 | == 用于检查深拷贝后的对象是否具有与原始对象相同的值(它们通常是相同的)。 |
= | 用于创建引用。 | = 用于执行浅拷贝操作,例如copied_obj = copy.copy(original_obj) 。 | = 用于执行深拷贝操作,例如copied_obj = copy.deepcopy(original_obj) 。 |
注意事项:
is
用于比较两个对象的身份,即它们是否在内存中占据相同的位置。==
用于比较两个对象的值,即它们的内容是否相等。=
用于赋值,创建新的引用或者执行浅拷贝或深拷贝操作。- 对于不可变数据类型(如数字、字符串、元组),浅拷贝和深拷贝的效果通常是相同的,因为它们的值不能被改变。
- 对于可变数据类型(如列表、字典、集合),浅拷贝和深拷贝会有明显的不同,因为浅拷贝只复制最外层的容器,而深拷贝会递归复制所有层级。
在Python中,None
是一个单例对象,用于表示缺少值或默认情况。通常使用is
来检查一个变量是否为None
,因为is None
比== None
更加明确和快速。
五、总结
注意!注意!注意!虽然对于不可变数据类型(注意和可迭代区分,要有记忆隔离鸿沟)引用和浅拷贝效果相同,但是浅拷贝和引用不是一回事。
引用是直接指向同一个对象的指针,而浅拷贝是创建一个新对象,但它内部仍然包含对原始对象内部元素的引用。
在Python中,=
操作符默认是引用。当你使用=
将一个对象赋值给另一个变量时,你实际上是在创建一个新的引用,而不是复制对象本身。