Python中的赋值
Python是一种动态类型编程语言,与C/C++等静态类型编程语言不同,Python中的变量没有固定的类型,变量也无需提前声明分配内存空间,因此我们可以给任何变量赋值任何符合Python规范的类型值,比如
交互式Python代码:>>> var = 88 >>> var = 'hello world'
在C语言中,变量使用前需要提前声明并明确其类型(或声明和赋值可同一条语句执行),以便为其分配内存空间,比如
C代码:int a; a = 100;
但在C语言中一般不可直接将其他类型值赋值给不同类型的变量(可强制类型转换的除外),例如如下代码就会报错:
C代码:
int a;
a = "hello world";
在Python中将字面量或变量对另一个变量进行赋值可以理解为:给内存中的变量实体贴上一个标签。这个标签就是变量标识符。
交互式Python代码:>>> a = 100 >>> b = a >>> a is b True
上述代码表明变量
a
与变量b
是同一个对象,标识符a和标识符b都是内存中变量实体100的一个标签。在C语言中,因为每个变量都有自己的内存空间,变量赋值是将值复制到指定的内存中,因此进行赋值的两个变量只是值相同,但并不表示同一个对象。
C代码:#include <stdio.h> int a = 100; int b; b = a; if (&a == &b) { printf("yse\n"); } else { printf("no\n"); } // 输出:no
Python中函数的实参和形参之间以及返回值也是进行赋值操作的。
交互式Python代码:>>> a = 3 >>> b = 5 >>> def func(m, n): print(m is a) print(n is b) r = m + n print('id(r):', id(r)) return r >>> c = func(a, b) True True id(r): 10914624 >>> id(c) 10914624
根据上述代码可知,Python函数调用时形参会被实参进行赋值操作,即在执行
c = func(a, b)
时其实相当于在函数内部执行了m = a
和n = b
。那么就会出现一个问题,当实参为可变对象时,在函数内部就存在改变实参的风险。
交互式Python代码:>>> a = [1, 2] >>> def func(List): List.append(a[0] + a[1]) return List >>> c = func(a) >>> print(c) [1, 2, 3] >>> print(a) [1, 2, 3] >>> id(a) == id(c) True
上述代码中
a
为列表类型(可变对象),在函数内部List
添加了一个元素并返回。a
,List
和函数返回后的c
表示同一个对象,这是我们之前讨论过的。现在我们讨论的重点是在函数内部改变了函数的实参,当然在有些情况下,我们正是需要借助这种特性完成某种功能,这种情况我们暂且不讨论。但在实际的工作中,更多的情况是我们不想函数更改实参,针对这种情况我们应该怎样规避实参被更改的风险呢?有三种方法:尽量使用不可变类型作实参
当实参为不可变对象时,函数内部形参的操作不会影响实参。
交互式Python代码:>>> a = 55 >>> id(a) 10916224 >>> def func(value): print('id(value):', id(value), 'value: ', value) print(value is a) value = 99 print('id(value):', id(value), 'value:', value) print(value is a) >>> func(a) id(value): 10916224 value: 55 True id(value): 10917632 value: 99 False >>> print('id(a):', id(a), 'a:', a) id(a): 10916224 a: 55 上述代码中`a`为int类型(不可变类型),作为实参赋值给形参`value`,函数在执行`value = 99`之前`value`与`a`表示同一个对象,在执行之后`value`与`a`表示不同的对象,其实这很好理解就像我们前面讨论的,在执行赋值语句之后`value`标识符被“贴”到另一个内存变量实体99上了。
使用拷贝
- 以上两种都不想用?那就写代码的时候多注意吧
下面我就来讨论今天的第二个重点:拷贝。
Python中的拷贝
Python中的拷贝分为浅拷贝和深拷贝
浅拷贝
Python中的默认拷贝都是浅拷贝,这里我们就拿列表类型举个栗子,先看看列表有哪些拷贝方法,交互式Python代码:
>>> a = [1, 2, 3] >>> b = a.copy() >>> a [1, 2, 3] >>> b [1, 2, 3] >>> b is a False >>> c = list(a) >>> c [1, 2, 3] >>> c is a False >>> d = a[:] >>> d is a False
上述代码中显示出三种列表拷贝方法:
- 使用列表对象的
.copy()
方法 - 使用
list()
转换函数 - 使用列表分片
[:]
我们也可看到拷贝出来的对象与被拷贝对象不再是同一个对象,这也是拷贝的根本目的。当使用可变对象的拷贝进行函数传参可以避免函数内部修改实参。
交互式Python代码:>>> a = [1, 2, 3] >>> def func(List): List.append(100) List[0] = 'hello' return List >>> b = func(a.copy()) >>> b ['hello', 2, 3, 100] >>> a [1, 2, 3]
好像一切都很完美,达到了我们的目的。于是我们编写了下面这样的代码:
交互式Python代码:>>> a = [1, 'hello', 'world', [88, 99], 22] >>> def func(List): List.append('shawn') List[0] = 'simon' List[3][0] = 100 return List >>> b = func(a.copy()) >>> b ['simon', 'hello', 'world', [100, 99], 22, 'shawn'] >>> a [1, 'hello', 'world', [100, 99], 22]
有瑕疵啊,这运行结果和我们想象的不一样,我们想象着变量
a
索引为3的元素值仍为[88, 99]
,但输出并不是这样,问题出在了哪里?原因在于我们前面所说的Python中的默认拷贝均为浅拷贝,如果我们想要输出达到我们的预期,则需要深拷贝。- 使用列表对象的
深拷贝
深拷贝就是在每个层次都对可变对象进行拷贝,浅拷贝只对最外层可变对象进行拷贝。
我们先来看看赋值的情况即b = a
,如下图所示:
示意图a
与b
指向同一个对象,我们通过代码验证其正确性:
交互式Python代码:>>> a = [1, 2, [3, 4]] >>> b = a >>> id(b) == id(a) True >>> id(a[0]) == id(b[0]) True >>> id(a[1]) == id(b[1]) True >>> id(a[2]) == id(b[2]) True >>> id(a[2][0]) == id(b[2][0]) True >>> id(a[2][1]) == id(b[2][1]) True
再来看看浅拷贝的情况,先上图:
上图中为b = a[:]
示意图,b
为a
的浅拷贝,浅拷贝会将最外层可变对象进行拷贝,下面进行代码验证:
交互式Python代码:>>> a = [1, 2, [3, 4]] >>> b = a[:] >>> id(b) == id(a) False >>> id(a[0]) == id(b[0]) True >>> id(a[1]) == id(b[1]) True >>> id(a[2]) == id(b[2]) True >>> id(a[2][0]) == id(b[2][0]) True >>> id(a[2][1]) == id(b[2][1]) True
最后来看看深拷贝,深拷贝我们需要用到copy库的deepcopy,示意图如下:
接下来进行代码验证:
交互式Python代码:>>> from copy import deepcopy >>> >>> a = [1, 2, [3, 4]] >>> b = deepcopy(a) >>> id(a) == id(b) False >>> id(a[0]) == id(b[0]) True >>> id(a[1]) == id(b[1]) True >>> id(a[2]) == id(b[2]) False >>> id(a[2][0]) == id(b[2][0]) True >>> id(a[2][1]) == id(b[2][1]) True
因此当实参为可变对象且其中含有多层嵌套的可变对象时,如果我们不想因为我们的粗心让函数更改实参,那么我们就应该使用深拷贝,看代码:
交互式Python代码:>>> a = [1, 2, [3, 4]] >>> def func(List): List.append('hello world') List[0] = 'shawn' List[2][0] = 'simon' List[2][1] = 199 return List >>> from copy import deepcopy >>> b = func(deepcopy(a)) >>> a [1, 2, [3, 4]] >>> b ['shawn', 2, ['simon', 199], 'hello world']
Note: Python中的深拷贝和浅拷贝只是针对可变对象而言,而对于不可变对象无所谓深拷贝还是浅拷贝,因为对于不可变对象两种拷贝和变量之间赋值没什么两样。
交互式Python代码:>>> from copy import deepcopy >>> >>> a = 'hello world' >>> b = deepcopy(a) >>> c = a >>> id(a) == id(b) == id(c) True
总结
Python中变量之间的赋值,相当于给变量多贴了一个“标签”,或者说给变量起了个别名
深拷贝、浅拷贝是针对可变对象说的,不可变对象无所谓什么拷贝
对于可变对象,深拷贝对每层可变对象都进行拷贝,而浅拷贝只对最外层进行拷贝
当函数实参为可变对象且我们不想函数改变实参本身,应尽量使用深拷贝,尤其在实参中嵌套了可变对象时