Python 赋值与拷贝

简介: Python 赋值与拷贝

变量的赋值

在 Python 中,要创建一个列表 [1, 2, 3] 并赋值给变量 a 的语法是这样的:a = [1, 2, 3]。通常我们称 a变量名[1, 2, 3]变量的值

给一个变量赋值的操作实际上就是将一个变量名指向一个对象,a = [1, 2, 3] 就相当于将变量名 a 指向 [1, 2, 3] 这个列表对象。此时将变量 a 再赋值给变量 bb = a,相当于将变量 b 也指向 [1, 2, 3] 列表。最终 ab 指向的是同一个 [1, 2, 3] 列表。

>>> a = [1, 2, 3]
>>> b = a
>>> a is b
True
>>> a.append(4)
>>> a
[1, 2, 3, 4]
>>> b
[1, 2, 3, 4]

在 Python 中可以通过 is 来比较两个变量是否为同一个对象,所以 ab 本质上是同一个对象,只不过是这个对象的两个 名字 罢了。

此时,将列表 [4, 5, 6] 赋值给变量 aa = [4, 5, 6],再次对变量 a 进行修改,会得到什么样的结果呢?

>>> a = [4, 5, 6]
>>> a.append(7)
>>> a
[4, 5, 6, 7]
>>> b
[1, 2, 3, 4]

可以看到,将变量 a 重新赋值以后,再次更改 a 的值,b 的值并没有跟着变化。实际上,在执行 a = [4, 5, 6] 的时候,a 的指针变成了指向新的列表 [4, 5, 6],而 b 的指针并没有改变,依旧指向原来的列表 [1, 2, 3, 4]

我们可以用示例图来看下这个过程。

  1. Python 解释器在执行 a = [1, 2, 3] 时,变量 a 通过一个指针指向列表 [1, 2, 3]

1

  1. 在执行 b = a 时,变量 b 也通过一个指针指向列表 [1, 2, 3]

2

  1. 在执行 a.append(4) 时,实际上就是在更改变量 a 和变量 b 所指向的同一个列表对象。

3

  1. 将变量 a 重新赋值,变量 b 依旧指向原来的列表对象。

4

  1. 此时再次对变量 a 进行更改 a.append(7),变量 b 的值不变。

5

对象的拷贝

由上面的赋值操作我们可以知道,在 Python 中如果将一个变量赋值给另一个变量,那么它们最终将指向同一个对象。然而有些时候我们需要创建一个与原对象有着相同值的新对象,但是不想让它们还是同一个对象,即如果改变其中一个对象,那么另一个对象不会跟着改变。这个时候,就需要用到对象的拷贝的知识了。

对于拷贝,Python 专门提供了一个 copy 模块。拷贝可以分为 浅拷贝深拷贝

  1. 先来看看浅拷贝
>>> import copy
>>> a = [1, 2, 3]
>>> b = copy.copy(a)
>>> b
[1, 2, 3]
>>> a == b
True
>>> a is b
False

copy.copy 就是 Python 提供的浅拷贝方法。浅拷贝会在内存中重新创建一个新的对象,但对象中内部的元素还是对原对象内部元素的引用。

来看下面的例子:

>>> import copy
>>> a = [[1, 2, 3], [4, 5, 6]]
>>> b = copy.copy(a)
>>> b
[[1, 2, 3], [4, 5, 6]]
>>> id(a)
14800096
>>> id(b)
14799136
>>> a.append(11)
>>> a[0].append(22)
>>> a
[[1, 2, 3, 22], [4, 5, 6], 11]
>>> b
[[1, 2, 3, 22], [4, 5, 6]]

上面的例子中,首先创建了一个列表 a,其内部还嵌套了另外两个列表,然后对 a 进行了 浅拷贝 操作,得到 b

执行 a.append(11) 操作的时候,不会对 b 产生影响,这是 浅拷贝 的性质决定的,b 是一个新的对象。

执行 a[0].append(22) 的时候,b 的值会跟着改变,这就是上面所说的,浅拷贝时,新对象中内部的元素还是对原对象内部元素的引用。

由此可见,浅拷贝 只是对嵌套对象的顶层拷贝,并不会拷贝其内部元素。因此有些时候操作还是会产生一些副作用,要想完全复制一个对象,就需要用到 深拷贝

  1. 深拷贝

Python 提供了 copy.deepcopy 可以对对象进行深度拷贝。深拷贝 同样会创建一个新的对象,并且还会将原对象内部的元素以递归的方式进行完全拷贝。这样就实现了完全复制一个对象的目的。

>>> import copy
>>> a = [[1, 2, 3], [4, 5, 6]]
>>> b = copy.deepcopy(a)
>>> b
[[1, 2, 3], [4, 5, 6]]
>>> id(a)
14799976
>>> id(b)
14800096
>>> a.append(11)
>>> a[0].append(22)
>>> a
[[1, 2, 3, 22], [4, 5, 6], 11]
>>> b
[[1, 2, 3], [4, 5, 6]]

使用 深拷贝,当改变变量 a 的值的时候,就不会对变量 b 的值产生影响了。这就是所谓的 深拷贝

上面已经对 深拷贝浅拷贝 进行了大致的介绍,不过还有些细节部分需要我们注意。

>>> import copy
>>> a = (1, 2, 4)
>>> b = copy.copy(a)
>>> c = copy.deepcopy(a)
>>> b
(1, 2, 4)
>>> c
(1, 2, 4)
>>> id(a)
52975576
>>> id(b)
52975576
>>> id(c)
52975576
>>> a is b is c
True

出现以上现象其实是因为元组为不可变对象,因此无需重新分配内存并创建一个新的对象。所以,对于不可变对象来说,浅拷贝深拷贝 最终的结果相同,都是指向原有的对象。但也有例外,就是当元组内部嵌套可变类型的时候。

>>> import copy
>>> a = ([1, 2], 3)
>>> b = copy.copy(a)
>>> c = copy.deepcopy(a)
>>> a[0].append(33)
>>> a
([1, 2, 33], 3)
>>> b
([1, 2, 33], 3)
>>> c
([1, 2], 3)
>>> id(a)
53628080
>>> id(b)
53628080
>>> id(c)
53628560

上面示例的结果其实并没有脱离 浅拷贝深拷贝 的特性,只不过是由于顶层对象是不可变类型,没有复制的必要,所以 浅拷贝 的对象和原对象还是同一个对象,原对象改变,浅拷贝 的对象也跟着改变。但是由于元组内部嵌套的对象是列表,列表又是可变类型,所以 深拷贝 就会递归的对其进行拷贝。

其实拷贝对于不可变对象来说,作用不大,真正有用的地方是在于可变对象的拷贝操作,并且只有被拷贝对象为嵌套对象的时候,才能够体现出差异。浅拷贝 只是对对象的顶层拷贝,深拷贝 是对对象的完全拷贝。

对于序列的 切片 操作,数据类型的构造器,实际上它们都相当于 浅拷贝

>>> a = [1, 2, 3]
>>> b = a[:]
>>> b
[1, 2, 3]
>>> a is b
False
>>> a = [1, 2, 3]
>>> b = list(a)
>>> b
[1, 2, 3]
>>> a is b
False

对于自定义对象,还可以通过实现 __copy____deepcopy__ 两个方法来定义 浅拷贝深拷贝 的结果。

import copy
class C(object):
    def __str__(self):
        return 'c'
    def __copy__(self):
        return 'copy c'
    def __deepcopy__(self, memodict={}):
        return 'deepcopy c'
c = C()
print(c)
c1 = copy.copy(c)
print(c1)
c2 = copy.deepcopy(c)
print(c2)
c
copy c
deepcopy c

你并不需要死记硬背来记下拷贝的特性,实际上,你只需要了解 Python 中可变类型和不可变类型的特点,就能够很容易理解了。在实际工作中,你可以根据自己的需要,使用 浅拷贝 或者 深拷贝 来解决遇到的问题。

相关文章
|
1月前
|
Python
python增量赋值运算的应用
Python中的增量赋值运算符用于便捷地执行算术操作,如`+=`, `-=`等,它们分别代表加法、减法、乘法、除法、取模、整除和幂运算。
15 1
|
2月前
|
存储 安全 Java
解释Python中的引用和赋值机制。
Python中,变量是对象引用,不存储数据,而存数据在内存的位置。赋值(=)创建变量并让其指向内存中的对象。当多个变量指向同一对象时,它们共享引用。Python使用引用计数管理对象生命周期,对象引用为0时回收。了解这些机制对优化内存使用和防止内存泄漏很重要。例如: ```markdown ```python a = 5 b = a # b引用了同一数字5 del a # 数字5的引用计数仍为1,未被回收 ``` 引用计数并非唯一机制,Python还采用其他策略处理循环引用等复杂情况。
18 2
|
2月前
|
存储 Python
Python系列(11)—— 赋值运算符
Python系列(11)—— 赋值运算符
C4.
|
2月前
|
Python
Python的赋值语句
Python的赋值语句
C4.
14 0
|
3天前
|
存储 Python
【Python操作基础】系列——赋值语句
【Python操作基础】系列——赋值语句
|
7天前
|
Python
Python中赋值使地址一样的技术探究
Python中赋值使地址一样的技术探究
20 0
|
12天前
|
NoSQL Serverless Python
在Python的Pandas中,可以通过直接赋值或使用apply函数在DataFrame添加新列。
【5月更文挑战第2天】在Python的Pandas中,可以通过直接赋值或使用apply函数在DataFrame添加新列。方法一是直接赋值,如`df['C'] = 0`,创建新列C并初始化为0。方法二是应用函数,例如定义`add_column`函数计算A列和B列之和,然后使用`df.apply(add_column, axis=1)`,使C列存储每行A、B列的和。
40 0
|
27天前
|
开发者 索引 Python
Python中的海象运算符:简洁而强大的赋值表达式
【4月更文挑战第17天】Python 3.8 引入了海象运算符 `:=`,也称赋值表达式运算符,用于在表达式内部赋值,简化代码并提升可读性。它能用于条件判断、循环控制和函数参数等场景,优化逻辑流程。然而,使用时需注意可读性、运算符优先级及赋值限制,以确保代码清晰易懂。海象运算符是Python编程的一个有用工具,但应根据情况谨慎使用。
|
1月前
|
存储 Python
python返回多个值与赋值多个值
python返回多个值与赋值多个值
18 0
|
2月前
|
存储 关系型数据库 程序员
Python变量赋值
Python变量赋值
15 0