概述

在本篇文章中,会先介绍 Python 中对象的基础概念,之后会提到对象的深浅拷贝以及区别。在阅读后,应该掌握如下的内容:

  • 理解变量、引用和对象的关系

  • 理解 Python 对象中 identity,type 和 value 的概念

  • 什么是 mutable 和 immutable 对象?以及它们和 hashable 的关系

  • 深浅拷贝的过程以及区别

变量,引用和对象

变量无类型,它的作用仅仅在某个时候引用了特定的对象而已,具体在内存中就是一个指针,仅仅拥有指向对象的空间大小。

变量和对象的关系在于引用,变量引用对象后,也就对应了赋值的过程。

在 python 中一切皆为对象,具体在内存中表示一块内存空间,每一个对象都会具有 identity,type 和 value 这三个内容。

Identity, 一旦对象被创建后,Identity 的值便不会发生改变。在 Cpython 中,其值体现为内存中保存对象的地址。is 操作符,比较对象是否相等就是通过这个值。通过 id() 函数查看它的整数形式。

Type, 和 Identity 一样,在对象创建后,Type 也不会发生变化。它主要定义了一些可能支持的值和操作(如对列表来说,会有求长度的操作)。通过 type() 函数可以得到对象的类型。

Value,用于表示的某些对象的值。当对象在创建后值可以改变称为 mutable,否则的话被称为 immutable.

举个例子,比如在 C 中,int x = 4 在内存中,是先分配了一个 int 类型的内存空间,然后把 4 放进空间内。

而 Python 中,x = 4 正好相反,是为 4 分配了一块的内存空间,然后用 x 指向它。由于变量可以指向各种类型的对象,因此不需要像 C 一样声明变量。这也就是 Python 被称为动态类型的意义。

并且在 Python 中,变量可以删除,但对象是无法删除的

immutable 和 mutable 对象

immutable 对象拥有一个固定的值,包括 numbers, strings, tuples. 一个新的值被保存时,一个新的对象就会被创建。这些对象在作为常量的 hash 值中有着非常重要的作用,如作为字典的 key 时。

mutable 对象可以改变自身的值,但 id() 并不会发生改变。

当一些对象包含对其他对象的一些引用时,我们称这些对象为 containers, 例如 list, tuple, dictionary 这些都是 containers. 这里需要注意的是,一个 immutable containers 可以包含对 mutable 对象的引用(如在 tuple 中包含一个 list)。 但这个对象仍然称为 immutable 对象,因为 Identity 是不变的。

hashable 对象

当一个对象在生命周期内(实现了 __hash__() 方法)hash 值不会发生改变,并可以与其他对象进行比较(实现了 __eq__() 方法),称之为hashable 对象。

在 Python 内置的 immutable 对象 大多数都是 hashable 对象。immutable containers(tuples, frozenset)在引用的对象都是 hashable 对象时,才是hashable 对象。mutable containers 容器都不是 hashable 对象。用户自定义的类都是 hashable 对象,

浅拷贝与深拷贝

在介绍对象的拷贝前,先介绍一下 Python 中的赋值操作,可以让我们更好的了解拷贝的过程。

赋值操作

赋值操作的右边是简单表达式:

def normal_operation():

    # immutable objects
# int
a = 10
b = 10
print('----- int')
print("id of a:{} , id of b: {}".format(id(a), id(b)))
# id of a:1777364320 , id of b: 1777364320
print(a == b) # True
print(a is b) # True
# str
str_a = '123'
str_b = '123'
print('----- str')
print("id of a:{} , id of b: {}".format(id(str_a), id(str_b)))
# id of a:1615046978224 , id of b: 1615046978224
print(str_a == str_b) # True
print(str_a is str_b) # True # tuple
tuple_a = (1, 2, 3)
tuple_b = (1, 2, 3)
print('----- tuple')
print("id of a:{} , id of b: {}".format(id(tuple_a), id(tuple_b)))
# id of a:1615047009696 , id of b: 1615047024856
print(tuple_a == tuple_b) # True
print(tuple_a is tuple_b) # False # mutable
# set
set_a = {1, 2, 3}
set_b = {1, 2, 3}
print('----- set')
print("id of a:{} , id of b: {}".format(id(set_a), id(set_b)))
# id of a:1615045625000 , id of b: 1615047012872
print(set_a == set_b) # True
print(set_a is set_b) # False # list
list_a = [1, 2, 3]
list_b = [1, 2, 3]
print('----- list')
print("id of a:{} , id of b: {}".format(id(list_a), id(list_b)))
# id of a:1615047017800 , id of b: 1615045537352
print(list_a == list_b) # True
print(list_a is list_b) # False # dict
dict_a = {"name": "xxx", "age": "123"}
dict_b = {"name": "xxx", "age": "123"}
print('----- dict')
print("id of a:{} , id of b: {}".format(id(dict_a), id(dict_b)))
# id of a:1615045521696 , id of b: 1615045522128
print(dict_a == dict_b) # True
print(dict_a is dict_b) # False

在 Cpython 中,id() 反映了对象在内存中的地址。可以看到,对于 immutable 对象中的 numberstring 来说,CPython 本身对其做了一定的优化,在创建相同的内容时,使其 指向了相同的内存地址,从而被复用。

但是,Python 不会对所有 mutable 对象执行此操作,因为实现此功能需要一定的运行时成本。对于在内存中的对象来说,必须首先在内存中搜索对象(搜索意味着时间)。对于 numberstring 来说,搜索到它们很容易,所以才对其做了这样的优化。

对于其他类型的对象,虽然创建的内容相同,但都在内存中完全创建了一块新的区域。

赋值操作的右边是 Python 中已存在的变量:

def assignment_operation():
# immutable objects
# int
a = 10
b = a
print('----- int')
print("id of a:{} , id of b: {}".format(id(a), id(b)))
# id of a:1777364320 , id of b: 1777364320
print(a == b) # True
print(a is b) # True
# str
str_a = '123'
str_b = str_a
print('----- str')
print("id of a:{} , id of b: {}".format(id(str_a), id(str_b)))
# id of a:2676110142128 , id of b: 2676110142128
print(str_a == str_b) # True
print(str_a is str_b) # True # tuple
tuple_a = (1, 2, 3)
tuple_b = tuple_a
print('----- tuple')
print("id of a:{} , id of b: {}".format(id(tuple_a), id(tuple_b)))
# id of a:2676110191640 , id of b: 2676110191640
print(tuple_a == tuple_b) # True
print(tuple_a is tuple_b) # True # mutable
# set
set_a = {1, 2, 3}
set_b = set_a
print('----- set')
print("id of a:{} , id of b: {}".format(id(set_a), id(set_b)))
# id of a:2676108788904 , id of b: 2676108788904
print(set_a == set_b) # True
print(set_a is set_b) # True # list
list_a = [1, 2, 3]
list_b = list_a
print('----- list')
print("id of a:{} , id of b: {}".format(id(list_a), id(list_b)))
# id of a:2676110181704 , id of b: 2676110181704
print(list_a == list_b) # True
print(list_a is list_b) # True # dict
dict_a = {"name": "xxx", "age": "123"}
dict_b = dict_a
print('----- dict')
print("id of a:{} , id of b: {}".format(id(dict_a), id(dict_b)))
# id of a:2676079063328 , id of b: 2676079063328
print(dict_a == dict_b) # True
print(dict_a is dict_b) # True

而当赋值操作的右边是已经存在的 Python 对象时,不论是什么类型的对象,都没有在内存中创建新的内容,仅仅是声明了一个新的变量指向之前内存中已经创建的对象,就像提供了一个别名一样。

改变赋值后的对象:

def assignment_operation_change():
# immutable objects
# int
a = 10
print("id of a:{}".format(id(a)))
# id of a:1994633728
b = a
a = a + 10
print('----- int')
print("id of a:{} , id of b: {}".format(id(a), id(b)))
# id of a:1994634048 , id of b: 1994633728
print(a == b) # False
print(a is b) # False # mutable objects
# list
list_a = [1, 2, 3]
list_b = list_a
list_a.append(4)
print('----- list')
print("id of a:{} , id of b: {}".format(id(list_a), id(list_b)))
# id of a:2676110181704 , id of b: 2676110181704
print(list_a == list_b) # True
print(list_a is list_b) # True

当修改 imutable 对象时,由于其本身不可改变,只能在内存中新申请一块新的空间,用于存储修改后的内容。对应上面 a=20 的操作,这时再判断 a 和 b 时,由于指向了内存的不同位置,所以 a,b不在相等。a 原来指向的内存区域不会被回收,因为现在由 b 指向。可以看到 b 指向的内存地址和 a 之前的指向的内存地址是一致的。

当修改 mutable 对象时,由于都指向相同的内存地址,所以对变量 list_a 修改的操作,也会映射到变量 list_b。

总结一下:

  • 指向 imutable 的不同变量,当其中一个变量被修改时,其他变量不受影响,因为被修改后的变量会指向一个新创建的对象。

  • 指向 mutable 对象的不同变量,当其中一个变量修改这个对象时,会影响到指向这个对象的所有变量。

浅拷贝

浅拷贝创建了一个对象,这个对象包含了对被拷贝元素的参考。 所以当使用浅拷贝来复制 conainters 对象时,仅仅拷贝了那些嵌套元素的引用。

def shallow_copy():
# immutable objects
# int
a = 10
b = copy(a)
print('----- int')
print("id of a:{} , id of b: {}".format(id(a), id(b)))
# id of a:1777364320 , id of b: 1777364320
print(a == b) # True
print(a is b) # True
# str
str_a = '123'
str_b = copy(str_a)
print('----- str')
print("id of a:{} , id of b: {}".format(id(str_a), id(str_b)))
# id of a:2676110142128 , id of b: 2676110142128
print(str_a == str_b) # True
print(str_a is str_b) # True # tuple
tuple_a = (1, 2, 3)
# Three methods of shallow copy
# tuple_b = tuple_a[:]
# tuple_b = tuple(tuple_a)
tuple_b = copy(tuple_a)
print(id(tuple_b))
print('----- tuple')
print("id of a:{} , id of b: {}".format(id(tuple_a), id(tuple_b)))
# id of a:2676110191640 , id of b: 2676110191640
print(tuple_a == tuple_b) # True
print(tuple_a is tuple_b) # True # mutable
# set
set_a = {1, 2, 3}
# Two methods of shallow copy
# set_b = set(set_a)
set_b = copy(set_a)
print('----- set')
print("id of a:{} , id of b: {}".format(id(set_a), id(set_b)))
# id of a:2099885540520 , id of b: 2099888490984
print(set_a == set_b) # True
print(set_a is set_b) # False # list
list_a = [1, 2, 3]
# Three methods of shallow copy
# list_b = list_a[:]
# list_b = list(list_b)
list_b = copy(list_a)
print('----- list')
print("id of a:{} , id of b: {}".format(id(list_a), id(list_b)))
# id of a:2099888478280 , id of b: 2099888478472
print(list_a == list_b) # True
print(list_a is list_b) # False # dict
dict_a = {"name": "xxx", "age": "123"}
# Two methods of shallow copy
# dict_b = dict(dict_a)
dict_b = copy(dict_a)
print('----- dict')
print("id of a:{} , id of b: {}".format(id(dict_a), id(dict_b)))
# id of a:2099855880480 , id of b: 2099886881024
print(dict_a == dict_b) # True
print(dict_a is dict_b) # False

这里有一点需要注意,对于 stringnumber 来说,正如上面提到的 Cpython 做了相应的优化,让不同的变量指向了相同的内存地址,进而 id 的值是相等的。

但对于元组这个 immutable 元素来说,执行 浅拷贝时,也不会创建一个内存区域,只是返回一个老元组的引用。

对于其他的 mutable 对象,在浅拷贝后都会创建一个新的内存区域,包含了被拷贝元素的引用。

浅拷贝正如它的名字那样,当拷贝嵌套的 mutable 元素时,就会出现问题:

def shallow_copy_change_value():
# list
list_a = [1, 2, 3, [4, 5, 6]]
list_b = copy(list_a)
list_a[0] = 10
list_a[3].append(7)
print('----- list')
print("ia:{} ,b: {}".format(list_a, list_b))
print("id of a:{} , id of b: {}".format(id(list_a), id(list_b)))
# a:[10, 2, 3, [4, 5, 6, 7]] ,b: [1, 2, 3, [4, 5, 6, 7]]
# id of a:1698595158472 , id of b: 1698595159752
print(list_a == list_b) # False
print(list_a is list_b) # False

下面是对上面 list 浅拷贝的图解:

执行浅拷贝操作:

list_b 执行浅拷贝后,创建一个新的对象,新对象中的 list_a[0] 指向 1.

修改 list_a 操作:

当执行 list_a[0] = 10 操作时,由于 list_a[0] 本身是 number 类型,会重新创建一块区域,用于保存新的值 10. 而新创建的 list_b[0] 并不会受到影响,还会指向之前的内存区域。

当修改list_a[3] 操作时,由于list_a[3] 在浅拷贝后,新创建的对象中不会 嵌套创建 一个新的 list_a[3] 对象,仅仅是指向了之前的 list_a[3] 对象。所以当修改 list_a[3] 时, list_b[3] 也会收到影响。

深拷贝

对于深拷贝操作来说,除了会创建一个新的对象外,会还递归的遍历老对象的中的嵌套元素,并形成新的副本。

def shallow_deepcopy_change_value():
# list
list_a = [1, 2, 3, [4, 5, 6]]
list_b = deepcopy(list_a)
list_a[0] = 10
list_a[3].append(7)
print('----- list')
print("a:{} ,b: {}".format(list_a, list_b))
print("id of a:{} , id of b: {}".format(id(list_a), id(list_b)))
# id of a:2099888478280 , id of b: 2099888478472
print(list_a == list_b) # False
print(list_a is list_b) # False

下面是对应图解过程:

执行深拷贝操作:

修改 list_a 操作:

这里 list_alist_b 已经是完全的不同的两个对象。

总结

在这篇文章中,主要介绍了 Python 中对象,以及对象的拷贝过程,主要有下面几个重要的内容:

  • Python 中变量没有类型,仅仅可看做一个指针,通过引用指向对象。变量可以删除,但对象不行。

  • Python 对象被创建后,会拥有 identity,type 和 value 三个属性。

  • immutablemutable,主要在于 value 在其生命周期内是否能发生变化。

  • 修改 mutable 对象时,所有指向它的变量都会受到影响。修改 immutable 对象时,指向它的其他变量没有影响。

  • immutable 的大多数对象都是 hashable,但要考虑 immutable containers 的特殊情况。

  • 浅拷贝会创建一个新的内存区域(对象),但其内部是对原对象内部引用的拷贝,在使用 mutable 对象时,存在一定的风险。

  • 深拷贝不但会创建一个新的内存区域(对象),还会递归的创建原对象的所有嵌套对象,但也带来了一些效率的问题。

参考

Assignment statements in Python are more interesting than you might think

mutable-vs-immutable-objects

term-immutable

03-semantics-variables

关于 Python 对象拷贝的那点事?的更多相关文章

  1. Python对象拷贝——深拷贝与浅拷贝

    对象赋值 浅拷贝 深拷贝 1. 对象赋值 对象的赋值实际上是对对象的引用.也就是说当把一个对象赋值给另一个对象时,只是拷贝了引用.如: >>> t1 = tuple('furzoom ...

  2. Python 对象的引用计数和拷贝

    Python 对象的引用计数和拷贝 Python是一种面向对象的语言,包括变量.函数.类.模块等等一切皆对象. 在python中,每个对象有以下三个属性: 1.id,每个对象都有一个唯一的身份标识自己 ...

  3. 《Python CookBook2》 第四章 Python技巧 对象拷贝 && 通过列表推导构建列表

    (先学第四章) 对象拷贝 任务: Python通常只是使用指向原对象的引用,并不是真正的拷贝. 解决方案: >>> a = [1,2,3] >>> import c ...

  4. python中的对象拷贝

    python中.进行函数參数传递或者返回值时,假设是一般的变量,会拷贝传递.假设是列表或字典则是引用传递.那python怎样对列表和字典进行拷贝传递呢:标准库的copy模块提供了两个方法:copy和d ...

  5. 【转】python中的对象拷贝

    转自:https://www.cnblogs.com/bhlsheji/p/5352330.html python中.进行函数參数传递或者返回值时,假设是一般的变量,会拷贝传递.假设是列表或字典则是引 ...

  6. python中拷贝对象的区别

    一.赋值.引用 在python中赋值语句总是建立对象的引用值,而不是复制对象.因此,python变量更像是指针,而不是数据存储区域 这点和大多数语音类似吧,比如C++.Java等 1.先看个例子: v ...

  7. Python 关于拷贝(copy)汇总(列表拷贝 // 字典拷贝 // 自定义对象拷贝)

    1.列表拷贝 引用是指保存的值为对象的地址.在 Python 语言中,一个变量保存的值除了基本类型保存的是值外,其它都是引用,因此对于它们的使用就需要小心一些.下面举个例子: 问题描述:已知一个列表, ...

  8. Python比较操作符、变量赋值、对象拷贝

    Python比较操作符.变量赋值.对象拷贝 目录 Python比较操作符.变量赋值.对象拷贝 1. 比较操作符 == 和 is 1.1 区别 1.2 实例 2. 变量及其赋值 2.1 概念和逻辑关系 ...

  9. Python开发【第二章】:Python深浅拷贝剖析

    Python深浅拷贝剖析 Python中,对象的赋值,拷贝(深/浅拷贝)之间是有差异的,如果使用的时候不注意,就可能产生意外的结果. 下面本文就通过简单的例子介绍一下这些概念之间的差别. 一.对象赋值 ...

随机推荐

  1. java 连续数字数组分组

    问题: 1. 将Lis list = Arrays.asList(1,2,3,5,8,9,10), 拆分成 [1,2,3] .[5]. [8,9,10] , 2. 再传入一个数字 9, 将匹配数字9的 ...

  2. 微信小程序--获取用户地理位置名称(无须用户授权)的方法

    准备 1.在http://lbs.qq.com/网站申请key 2.在微信小程序后台把apis.map.qq.com添加进request合法域名 效果 添加封装 /** * 发起网络请求 * @par ...

  3. Prometheus(二):Prometheus 监控Windows机器

    一.安装wmi-exporter 首先在需要监控的Windows机器上安装wmi_exporter.wmi_exporter下载地址:https://github.com/martinlindhe/w ...

  4. Java源码 HashMap.roundUpToPowerOf2原理

    int rounded = number >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : (rounded = Integer.highestOneBit(nu ...

  5. 在docker中创建使用MySQL,并实现远程连接navicat

    在 docker 中使用 mysql 安装完docker之后,在命令行中输入docker images可以查看自己创建的image(安装下载docker的教程很多了,大家需要可以去查一下就可以了)这里 ...

  6. [2018-07-4] django笔记

    新建app python ..\venv\scripts\django-admin.py startapp cy python manage.py makemigrations python mana ...

  7. web中间件常见漏洞总结笔记

    之前看吐司别人发的个文档,简单记的笔记 ----- IIS     解析漏洞        IIS 6            *.asp;.jpg会被当作asp解析            *.asp/ ...

  8. [考试反思]0811NOIP模拟测试17:虚无

    (sdfz未参加,也就是一共就51个人) 也不粘具体排名了,只写分数线. []220 []201 []194 [5]181 [10]141 [15]132 [20]122 [25]116 [30]10 ...

  9. NOIP模拟 39

    考的嘛也不是. 伤心(怎么可能) T1稍想想组合数,然后牢记: 取模题随时取模,包括刚刚读入的数据  T2想到了基环树,然而不会打QAQ.. 非常简洁但非常大神的做法:随便断掉环上的一条边 利用“这条 ...

  10. 基于xposed逆向微信、支付宝、云闪付来实现个人免签支付功能

    我的个人网站如何实现支付功能? 想必很多程序员都有过想开发一个自己的网站来获得一些额外的收入,但做这件事会遇到支付这个问题.目前个人网站通过常规手法是无法实现支付管理的,所有支付渠道都需要以公司的身份 ...