前言
今天在实验过程中,发现将字典作为函数的形参传入函数,在函数内改变形参,会导致传入的字典的值也发生相应的改变。
这与c++
不同,令我疑惑,遂写此文。
简单实验
我们对常见的数据类型进行实验,检测形参的该表是否会改变传入的实参。
变量
1 2 3 4 5 6 7 8 | def change(a): a = 2 print (a) b = 1 change(b) print (b) >> 2 >> 1 |
可见,变量的值并没有随形参的改变而改变
元组
1 2 3 4 5 6 7 8 | def change(a): a = a[: 2 ] print (a) b = ( 1 , 2 , 3 ) change(b) print (b) >>( 1 , 2 ) >>( 1 , 2 , 3 ) |
可见,元组没有随形参的改变而改变
列表
1 2 3 4 5 6 7 8 | def change(a): a.append( 1 ) print (a) b = [ 2 , 3 ] change(b) print (b) >>[ 2 , 3 , 1 ] >>[ 2 , 3 , 1 ] |
可见,列表随着形参的改变而发生改变
字典
1 2 3 4 5 6 7 8 | def change(a): a[ 1 ] = 100 print (a) b = { 1 : 1 , 2 : 2 } change(b) print (b) >>{ 1 : 100 , 2 : 2 } >>{ 1 : 100 , 2 : 2 } |
可见,字典随着形参的改变而发生改变
原因
我们遇到了可变和不可变数据类型之间的差异。在Python
中,数据类型可以是可变的,也可以是不可变的。
我们通常使用的(整数,浮点数,字符串,布尔值和元组)数据类型都是不可变的,但是列表和字典是可变的。
这意味着全局列表或字典即使在函数内部使用时也可以更改,我们通过上面的例子也能看出这个问题。
不可变数据举例
a = 1
变量a
的作用类似于一个指向 1 的指针。
变量的数据类型是不可变的,变量一旦创建就不能别改变。
如果我们执行 a = a + 1
。
我们实际上不是将 1 更新到 2,而是指针从 1 指向了 2。
可变数据距离
1 | list1 = [ 1 , 2 , 3 ] |
如果我们在列表的末尾添加一个值,我们不是将list1
指向另一个列表,而是直接更新现有列表。
如果我们创建多个列表变量,只要他们指向同一个列表,那么当列表发生改变时,这些列表变量都会发生变化。
1 2 3 4 5 6 7 | list1 = [ 1 , 2 , 3 ] list2 = list1 list1.append( 4 ) print (list1) print (list2) >>[ 1 , 2 , 3 , 4 ] >>[ 1 , 2 , 3 , 4 ] |
心得
从这两个例子中,我们可以直观感受到可变数据类型与不可变数据类型之间的区别。
我们对可变数据的操作,是直接在其本身上进行操作的。
对不可变数据的操作,是将指针指向另一个位置,而不是更改其本身。
保持可变数据不变
我们在写代码时,经常会编写各种函数。我们不希望传入函数的实参,会在函数内部被改变。那应该怎么办呢?
其实很简单,使用.copy()
方法复制列表或字典即可。
1 2 3 4 5 6 7 | list1 = [ 1 , 2 , 3 ] list2 = list1.copy() list1.append( 4 ) print (list1) print (list2) >>[ 1 , 2 , 3 , 4 ] >>[ 1 , 2 , 3 ] |
.copy()
方法会创建一个新的副本,这样list2
就不会指向list1
指向的列表,而是指向一个新的列表。
这样的话,list1
与list2
就相互独立,互不影响了。
1 2 3 4 5 6 7 8 9 | def change(a): temp = a.copy() temp.append( 4 ) print (temp) list1 = [ 1 , 2 , 3 ] change(list1) print (list1) >>[ 1 , 2 , 3 , 4 ] >>[ 1 , 2 , 3 ] |
可见,这样我们就解决了形参的改变带来的实参改变的问题。对于字典也是一样的。
补充
深拷贝与浅拷贝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | >>> import copy >>> origin = [ 1 , 2 , [ 3 , 4 ]] #origin 里边有三个元素:1, 2,[3, 4] >>> cop1 = copy.copy(origin) >>> cop2 = copy.deepcopy(origin) >>> cop1 = = cop2 # 判断 cop1 和 cop2 的值是否相同 True >>> cop1 is cop2 # 判断 cop1 和 cop2 是否是同一个对象 False #cop1 和 cop2 看上去相同,但已不再是同一个object >>> origin[ 2 ][ 0 ] = "hey!" >>> origin [ 1 , 2 , [ 'hey!' , 4 ]] >>> cop1 [ 1 , 2 , [ 'hey!' , 4 ]] >>> cop2 [ 1 , 2 , [ 3 , 4 ]] |
copy对于一个复杂对象的子对象并不会完全复制。什么是复杂对象的子对象呢?
比如列表里的嵌套列表(多维列表),字典里的嵌套字典(多维字典)都是复杂对象的子对象。
对于子对象,python
会将其当做一个公共镜像存储起来,所有对它的复制都会被当成引用(就是拿指针指向同一块区域)。所以,其中一个引用的值发生改变时,其他引用的值也会发生改变。
复制复杂对象,我们也想全盘复制(包括子对象),这时我们就可以使用deepcopy()
函数进行深拷贝
1 2 3 | import copy a = [ 1 , 2 , [ 3 , 4 ]] b = copy.deepcopy(a) # 深拷贝 |
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持IT俱乐部。