最新消息:XAMPP默认安装之后是很不安全的,我们只需要点击左方菜单的 "安全"选项,按照向导操作即可完成安全设置。

Python对象赋值、浅拷贝、深拷贝的区别

XAMPP案例 admin 716浏览 0评论

前言

  • Python 中不存在值传递,一切传递的都是对象的引用,也可以认为是传址
  • 这里会讲三个概念:对象赋值、浅拷贝、深拷贝

名词解释

  • 变量:存储对象的引用
  • 对象:会被分配一块内存,存储实际的数据,比如字符串、数字、列表
  • 引用:变量指向对象,可以理解为指针

drc01

实际的一种应用场景

  • 有一个变量 a,存储了一个值
  • 此时想用另一个变量 b 暂时存储变量 a 的值,以便后续使用
  • 然后继续修改变量 a 的值,但修改的时候并不想同步更改变量 b 的值
a=1
b=a
a=2

对象赋值

1. 赋值运算符详解:

赋值运算符

= 赋值
-= 减法赋值
=+ 加法赋值
*= 乘法赋值
/= 除法赋值
%= 求模赋值
**= 乘方赋值
//= 整数除赋值

Python 中不同数据类型的赋值

单个变量赋值单个对象

a = 1
b = 1.0
c = "字符串"
d = [1, 2, 3, 4]
e = (1, 2, 3, 4)
f = {1, 2, 3, 4}
g = {1: 1, 2: 2}

多个变量同时赋值多个对象

python 的特性,可以一行代码,同时给多个变量赋值

# 多变量
a, b = 1, 2
print(a, b)

a, b, c, d = 1, 2.0, True, "字符串"
print(a, b, c, d)

e, f, g, h = [1, 2, 3, 4], (1, 2, 3, 4), {1, 2, 3, 4}, {1: 1, 2: 2}
print(e, f, g, h)

# 输出结果
1 2
1 2.0 True 字符串
[1, 2, 3, 4] (1, 2, 3, 4) {1, 2, 3, 4} {1: 1, 2: 2}

单个变量赋值多个对象

这是 Python 元组的特性,单个变量使用 = 时,元组在右边的时候可以不用加 ( ),也称为元组打包

# 单个变量
a = 1, True, "字符串"
print(a, type(a))

# 输出结果
(1, True, '字符串') <class 'tuple'>

多个变量赋值单个序列对象

  • 这也叫序列解包,因为解包操作的 = 右侧可以是任何序列
  • 序列解包要求等号左侧的变量数与右侧序列里所含的元素数相同

a, b, c = (1, 2, 3)
print(a, b, c)

a, b, c = [1, 2, 3]
print(a, b, c)

a, b, c = {1, 2, 3}
print(a, b, c)

# 输出结果
1 2 3
1 2 3
1 2 3

总结

多重赋值其实就是元组打包和序列解包的组合

+=

等价写法

a +=1
a = a+1

其他赋值运算符同理,不再重复写

栗子

# +=
a = 1
a += 1
print(a)

a +=2.0
print(a)

# 输出结果
2
4.0

-=

# -=
a = 1
a -= 1
print(a)

a -= 1.0
print(a)

# 输出结果
0
-1.0

*=

# *=
a = 1
a *= 2
print(a)

a *= 2.0
print(a)

# 输出结果
2
4.0

**=

# **=
a = 2
a **= 2
print(a)

a **= 2.0
print(a)

# 输出结果
4
16.0

/=

# /=
a = 2
a /= 2
print(a)

a /= 2.0
print(a)

# 输出结果
1.0
0.5

//=

# //=
a = 5
a //= 2
print(a)

a //= 2.0
print(a)

# 输出结果
2
1.0

%=

# %=
a = 100
a %= 8
print(a)

a %= 3.0
print(a)

# 输出结果
4
1.0

2.  Python 的赋值语句并不是创建一个新对象,只是创建了一个共享原始对象引用的新变量

 

不可变对象的赋值

a = 1
b = a

print(a, b)

a += 2
print(a, b)

print("a id:", id(a))
print("b id:", id(b))


# 输出结果
1 1
2 1
a id: 4564097808
b id: 4564097776
  • 修改变量 a 的值,不会同步修改变量 b 的值
  • 因为赋值操作 a += 2 后,变量 a 存储的对象引用已经改变了
  • 至于具体的原理,可以看看不可变对象、可变对象的详解 https://www.cnblogs.com/poloyy/p/15073168.html

可变对象的赋值

a = [1, 2, 3]
b = a

print(a, b)

a[1] = 22
print(a, b)

print("a id:", id(a))
print("b id:", id(b))


# 输出结果
[1, 2, 3] [1, 2, 3]
[1, 22, 3] [1, 22, 3]
a id: 4567683136
b id: 4567683136
  • 修改 a 变量的值,会同步修改变量 b 的值,这不符合上面的说的实际应用场景
  • 因为变量 a、b 指向的对象是可变对象,所以它们保存的对象引用都是一样的

 

拷贝的诞生

  • 那如果要让可变对象也能满足上述实际应用场景,要怎么做呢?
  • 当然就是拷贝
  • 而拷贝又分为浅拷贝、深拷贝,接下来会具体聊一聊两种拷贝的区别

 

第一个重点总结

  • 对于不可变对象来说,赋值操作其实就可以满足上面说的实际应用场景
  • 所以!后面要讲的浅拷贝、深拷贝对于不可变对象来说,和赋值操作是一样的效果!
  • 记住!浅拷贝、深拷贝只针对可变对象,即列表、集合、字典!

 

copy 模块

Python 提供了 copy 模块,包含了浅拷贝、深拷贝函数

from copy import copy, deepcopy

# 浅拷贝
copy(x)

# 深拷贝
deepcopy(x)

 

浅拷贝

一句话概括:浅拷贝会创建一个新对象,该新对象存储原始元素的引用

 

浅拷贝后的值是相同的

  • 将列表赋值给变量 old_list
  • 通过 copy() 方法对 old_list 变量指向的对象进行浅拷贝,并赋值给新变量 new_list
  • 因为是对象进行拷贝,所以 new_list 和 old_list 存储的值是相同的
import copy

old_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
new_list = copy.copy(old_list)

print("Old list:", old_list)
print("New list:", new_list)


# 输出结果
Old list: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
New list: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

 

浅拷贝后的会产生一个新的对象

  • 虽然 old_list 和 new_list 存储的值是相同的,但浅拷贝的操作是产生了一个新的对象
  • 所以 old_list 和 new_list 指向的对象并不是同一个
import copy

old_list = [[1, 2], [3, 4]]
new_list = copy.copy(old_list)

old_list.append([5, 6])

print("Old list:", old_list, "id is :", id(old_list))
print("New list:", new_list, "id is :", id(new_list))


# 输出结果
Old list: [[1, 2], [3, 4], [5, 6]] id is : 4366240704
New list: [[1, 2], [3, 4]] id is : 4366246720

可以看到内存地址是不同的,所以给 old_list 新增一个元素并不会同步让 new_list 也新增

 

原理图

drc001

  • 浅拷贝生成了一个新对象,然后赋值给 new_list
  • new_list、old_list 指向的列表对象不是同一个,但值相同
  • 重点:对于列表对象中的元素,浅拷贝产生的新对象只存储原始元素的引用(内存地址),所以两个列表对象的元素的引用都指向同一个内存地址

 

那为什么要深拷贝呢?

修改列表内的不可变对象元素

上面的栗子是直接添加元素,来看看修改元素会怎么样

# 不可变元素
import copy

old_list = [1, 2, "string", (1, 2,)]
new_list = copy.copy(old_list)

old_list[1] += 22
old_list[2] += "s"
old_list[3] += (3,)

print("Old list:", old_list)
print("New list:", new_list)


# 输出结果
Old list: [1, 24, 'strings', (1, 2, 3)]
New list: [1, 2, 'string', (1, 2)]

修改 old_list 的三种不可变对象元素,均不会同步给 new_list

 

修改不可变对象的原理图

drc0001

修改列表内的可变对象元素

# 可变元素
import copy

old_list = [[1, 2], [3, 4]]
new_list = copy.copy(old_list)

old_list[0][0] += 99
old_list[1][0] += 97

print("Old list:", old_list, "old list id:", id(old_list), " old list[0] id:", id(old_list[0]))
print("new list:", new_list, "new list id:", id(new_list), " new list[0] id:", id(new_list[0]))


# 输出结果
Old list: [[100, 2], [100, 4]] old list id: 4430308096  old list[0] id: 4430302400
new list: [[100, 2], [100, 4]] new list id: 4430308416  new list[0] id: 4430302400

从输出结果看到

  • 两个变量保存了不同的对象引用
  • 但是可变对象元素的内存地址仍然是同一个

修改可变对象的原理图

drc00001

总结

  • 修改可变对象是在原始对象上直接操作的
  • 浅拷贝产生的新对象存储的仍然是原始对象的内存地址
  • 所以修改可变对象的时候,新对象的值也会被同步修改,因为新旧列表对象的元素的引用是指向同一个内存地址
  • 当修改可变对象的时候,不满足一开始说的实际应用场景,所以诞生了深拷贝

 

深拷贝

  • 创建一个新对象,且存储的对象引用也是新的
  • 深,意味着会把所有子元素对象也复制生成一个新对象

 

栗子一

# 深拷贝
old_list = [[1, 2], [3, 4]]
new_list = copy.deepcopy(old_list)

old_list[0][0] += 99
old_list[1][0] += 97

print("Old list:", old_list, "old list id:", id(old_list), " old list[0] id:", id(old_list[0]))
print("new list:", new_list, "new list id:", id(new_list), " new list[0] id:", id(new_list[0]))


# 输出结果
Old list: [[100, 2], [100, 4]] old list id: 4430308480  old list[0] id: 4430211392
new list: [[1, 2], [3, 4]] new list id: 4430308096  new list[0] id: 4430308864

从输出结果看到

  • 两个变量保存了不同的对象引用
  • 可变对象元素(子对象)的内存地址也是不同的

栗子二

假设是一个三维列表呢

# 深拷贝-三维数组
old_list = [[1, [10, 9]], [3, 4]]
new_list = copy.deepcopy(old_list)

old_list[0][1][0] += 90

print("Old list:", old_list)
print("New list:", new_list)


# 输出结果
Old list: [[1, [100, 9]], [3, 4]]
New list: [[1, [10, 9]], [3, 4]]

两个变量依旧是独立的

深拷贝原理图

drc000001

浅拷贝的多种实现方式

方式一:使用切片 [:]

列表

# 浅拷贝 [:]
old_list = [1, 2, [3, 4]]
new_list = old_list[:]

old_list.append(5)
old_list[2][0] += 97

print("Old list:", old_list, "old list id:", id(old_list), " old list[0] id:", id(old_list[2]))
print("new list:", new_list, "new list id:", id(new_list), " new list[0] id:", id(new_list[2]))

# 输出结果
Old list: [1, 2, [100, 4], 5] old list id: 4537660608  old list[0] id: 4537659840
new list: [1, 2, [100, 4]] new list id: 4537711424  new list[0] id: 4537659840

方式二:使用工厂函数

工厂函数简介

  • 工厂函数看上去像函数,但实际是一个类
  • 调用时,生成该数据类型类型的一个实例

 

可变对象的工厂函数

  • list()
  • set()
  • dict()

列表

# 浅拷贝 工厂函数
old_list = [1, 2, [3, 4]]
new_list = list(old_list)

old_list.append(5)
old_list[2][0] += 97

print("Old list:", old_list, "old list id:", id(old_list), " old list[0] id:", id(old_list[2]))
print("new list:", new_list, "new list id:", id(new_list), " new list[0] id:", id(new_list[2]))

集合

# 浅拷贝 工厂函数-集合
old_set = {1, 2, 3}
new_set = set(old_set)

old_set.add(4)

print("Old set:", old_set, "old set id:", id(old_set))
print("new set:", new_set, "new set id:", id(new_set))

# 输出结果
Old set: {1, 2, 3, 4} old set id: 4484723648
new set: {1, 2, 3} new set id: 4484723872

字典

# 浅拷贝 工厂函数-字典
old_dict = {"name": "小菠萝"}
new_dict = dict(old_dict)

old_dict["second"] = "测试笔记"

print("Old dict:", old_dict, "old dict id:", id(old_dict))
print("new dict:", new_dict, "new dict id:", id(new_dict))

# 输出结果
Old dict: {'name': '小菠萝', 'second': '测试笔记'} old dict id: 4514161536
new dict: {'name': '小菠萝'} new dict id: 4515690304

方式三:使用数据类型自带的 copy 方法

列表

# 浅拷贝 自带的copy方法-列表
old_list = [1, 2, [3, 4]]
new_list = old_list.copy()

old_list.append(5)
old_list[2][0] += 97

print("Old list:", old_list, "old list id:", id(old_list), " old list[0] id:", id(old_list[2]))
print("new list:", new_list, "new list id:", id(new_list), " new list[0] id:", id(new_list[2]))

# 输出结果
Old list: [1, 2, [100, 4], 5] old list id: 4309832000  old list[0] id: 4310372992
new list: [1, 2, [100, 4]] new list id: 4309735296  new list[0] id: 4310372992

集合

# 浅拷贝 自带的copy方法-集合
old_set = {1, 2, 3}
new_set = old_set.copy()

old_set.add(4)

print("Old set:", old_set, "old set id:", id(old_set))
print("new set:", new_set, "new set id:", id(new_set))

# 输出结果
Old set: {1, 2, 3, 4} old set id: 4309931392
new set: {1, 2, 3} new set id: 4309930944

字典

# 浅拷贝 自带的copy方法-字典
old_dict = {"name": "小菠萝"}
new_dict = old_dict.copy()

old_dict["second"] = "测试笔记"

print("Old dict:", old_dict, "old dict id:", id(old_dict))
print("new dict:", new_dict, "new dict id:", id(new_dict))

# 输出结果
Old dict: {'name': '小菠萝', 'second': '测试笔记'} old dict id: 4308452288
new dict: {'name': '小菠萝'} new dict id: 4308452224

源码

    def copy(self, *args, **kwargs): # real signature unknown
        """ Return a shallow copy of the list. """
        pass

已经写的很清楚,这是浅拷贝

 

方式四:使用 copy 模块的 copy 方法

列表

# 浅拷贝 copy模块的copy方法-列表
from copy import copy

old_list = [1, 2, [3, 4]]
new_list = copy(old_list)

old_list.append(5)
old_list[2][0] += 97

print("Old list:", old_list, "old list id:", id(old_list), " old list[0] id:", id(old_list[2]))
print("new list:", new_list, "new list id:", id(new_list), " new list[0] id:", id(new_list[2]))

# 输出结果
Old list: [1, 2, [100, 4], 5] old list id: 4381013184  old list[0] id: 4381159936
new list: [1, 2, [100, 4]] new list id: 4381012800  new list[0] id: 4381159936

集合

# 浅拷贝 copy模块的copy方法-集合
from copy import copy

old_set = {1, 2, 3}
new_set = copy(old_set)

old_set.add(4)

print("Old set:", old_set, "old set id:", id(old_set))
print("new set:", new_set, "new set id:", id(new_set))

# 输出结果
Old set: {1, 2, 3, 4} old set id: 4381115552
new set: {1, 2, 3} new set id: 4381115776

字典

# 浅拷贝 copy模块的copy方法-字典
from copy import copy

old_dict = {"name": "小菠萝"}
new_dict = copy(old_dict)

old_dict["second"] = "测试笔记"

print("Old dict:", old_dict, "old dict id:", id(old_dict))
print("new dict:", new_dict, "new dict id:", id(new_dict))

# 输出结果
Old dict: {'name': '小菠萝', 'second': '测试笔记'} old dict id: 4381159680
new dict: {'name': '小菠萝'} new dict id: 4379632576

面试题:浅拷贝、深拷贝的区别

  1. 浅拷贝和深拷贝只有在可变对象才会生效,不可变对象的赋值操作、浅拷贝、深拷贝的效果是一样的
  2. 浅拷贝会将对象复制生成一个新对象,但新对象仍然存储原始对象的引用,当原始对象是可变对象,然后修改它的值时,新旧对象会同时改变
  3. 深拷贝不仅会将对象复制生成一个新对象,且所有原始对象都会复制生成新对象,即使原始对象是可变对象,新对象存储的对象引用也是新的,所以改变旧对象的可变对象时,不会影响新对象
END

来源:小菠萝测试笔记

转载请注明:XAMPP中文组官网 » Python对象赋值、浅拷贝、深拷贝的区别

您必须 登录 才能发表评论!