小谈Python深浅拷贝及赋值
a = [1, 2, "hello", ['Python', 'C++']]
浅拷贝(shallow copy)
浅拷贝
- 浅拷贝会创建新的对象, 其内容非原对象本身的引用, 而是原对象内第一层对象的引用
- 浅拷贝有三种形式: 切片操作、工厂函数和copy模块中的copy函数
- 切片操作:
b = a[:]
或者b = [x for x in a]
- 工厂函数:
b = list(a)
- copy函数:
b = copy.copy(a)
- 切片操作:
- 浅拷贝产生的列表b不再是列表a了, 使用
is
判断可以发现他们不是同一个对象, 使用id()
查看, 它们指向的也不是同一片内存空间了。 但是当我们使用id(x) for x in a
和id(x) for x in b
来查看a和b中的元素的地址时, 可以看出而这包含的元素地址是相同的。 - 在这种情况下, 列表a和b是不同对象, 修改列表b理论上不会影响列表a。
注意
- 要注意的是浅拷贝之所以称之为浅拷贝, 是它仅仅只拷贝了一层, 在列表a中嵌套的list, 如果我们修改它, 情况就不一样了。
- 如:
a[3].append('Java')
, 再去查看列表b, 会发现列表b也发生了变化, 这是因为, 我们修改了嵌套的list, 修改外层元素, 会修改它的引用, 让它指向别的位置, 修改嵌套列表中的元素, 列表的地址不会发生变化, 指向的都是一个位置。
深拷贝(deep copy)
深拷贝
- 深拷贝和浅拷贝对应, 深拷贝拷贝了对象的所有元素, 包括多次嵌套的元素. 因此, 它的时间和空间开销要高。
- 同样对于列表a, 如果使用
b = copy.deepcopy(a)
, 再修改列表b将不会影响到列表a, 即使嵌套的列表在更深的层次, 也不会产生任何影响, 因为深拷贝拷贝出来的对象根本就是一个全新的对象, 不再与原来的对象有任何关联。
拷贝的注意点
- 对于非容器类型, 如数字,字符, 以及其他的"原子型", 没有拷贝一说, 产生的都是原对象的引用。
- 如果元祖变量值包含原子类型对象, 即使采用了深拷贝, 也只能得到浅拷贝。
赋值
- 在Python中, 对象的赋值就是简单的对象引用, 和C++不同, C++的赋值是调用赋值函数。
- 如下:
a = [1, 2, "hello", ['Python', 'C++']]
b = a
- 在上述情况下, a和b是一样的, 他们指向的是同一片内存, 但b不是a的别名, 是引用。
判断
- 我们可以用
b is a
去判断, 返回True
则表示他们地址相同, 内容也相同, 也可以使用id()
函数来查看两个列表的地址是否相同。 - 赋值操作(包括对象作为参数、返回值)不会开辟新的内存空间, 它只是复制了对象的引用. 也就是说除了b这个名字之外, 其没有其他的内存开销. 修改了a, 也就影响了b, 同理, 修改了b也就影响了a。
is 与 ==
is 与 ==
- Python中对象包含的三个基本要素, 分别是
id(身份标识符)
、type(数据类型)
和value(值)
。 is
和==
都是对对象进行比较判断作用的,但对对象比较判断的内容并不相同。
==
: 比较运算符
is
: 同一性运算符
区别
==
: 比较运算符, 在于判断两个对象的value(值)
是否相等。is
: 同一性运算符, 这个运算符比较判断的是对象间的唯一身份标识,也就是id是否相同, 涉及深浅拷贝, 且只有容器类型涉及深拷贝, 当构造新的容器类型值时, 都相当于创建开辟新的内存空间, 而不是变量值内存地址的引用, 而数字,字符及其他"原子"型对象在被创建是都是对变量值内存地址的引用。