Python 浅拷贝和深拷贝

一、前奏:熟悉Python内存管理

在Python中,变量在第一次赋值时自动声明,在创建---也就是赋值的时候,解释器会根据语法和右侧的操作数来决定新对象的类型。

引用计数器:一个内部跟踪变量

引用计数:每一个对象各有多少个引用

当对象被创建并(将其引用)赋值给变量时,该对象的引用计数就被设置为 1

>>> x = 3.14

语句 x=3.14,创建一个浮点型对象并将其引用赋值给了x,x是第一个引用,该对象的引用计数为1

当一个对象(的引用)又被赋值到其他变量,或做参数传递等,该对象的一个新的引用(或叫别名)被创建,则该对象的引用计数自动+1。

以下都会增加引用计数:

y = x   #做别名
foo(x)  #做参数传递
mylis = [1,2,x,‘a‘] #成为容器对象的一个元素

以下都会减少引用计数:

del x   #del显式销毁

bar = x
x = True    #对象的一个别名被赋值给其他对象

mylis.remove(x) #对象被从窗口对象中移除

del mylis   #窗口对象本身被销毁

二、Python的复制

从上面可见,对象的赋值实际上是对象的引用。当创建一个对象,然后把它赋给另一个变量的时候,python并没有拷贝这个对象,而只是拷贝了这个对象的引用。

当你对一个对象赋值的时候(做为参数传递,或者做为返回值),Python和Java一样,总是传递原始对象的引用,而不是一个副本。

"""传递原始对象的引用,而不是一个副本"""
a = [1,2,3]
b = a
b.append(100)
print b         #[1, 2, 3, 100]
print a         #[1, 2, 3, 100]
print id(a)     #11530368
print id(b)     #11530368 

如 果你想修改一个对象,而且想让原始的对象不受影响,那你就需要对象复制。

可以 使用copy.copy(),它可以进行对象的浅复制(shallow copy),它复制了对象,但对于对象中的元素,依然使用引用.

(1)、使用切片[:]操作进行拷贝

(2)、使用工厂函数(如list/dir/set)等进行拷贝

(3)、copy.copy()

>>> jack = [‘jack‘,[‘age‘,20]]
>>> tom = jack[:]
>>> anny = list(jack)
>>> jack
[‘jack‘, [‘age‘, 20]]
>>> tom
[‘jack‘, [‘age‘, 20]]
>>> anny
[‘jack‘, [‘age‘, 20]]
>>> print id(jack),id(tom),id(anny)
13457088 18487376 18489136

接下来修改上面例子,对姓名和年级进行修改:

>>> tom[0]=‘tom‘
>>> anny[0]=‘anny‘
>>> print tom
[‘tom‘, [‘age‘, 20]]
>>> print anny
[‘anny‘, [‘age‘, 20]]
>>> anny[1][1]
20
>>> anny[1][1]= 18
>>> anny[1][1]
18
>>> print jack,tom,anny
[‘jack‘, [‘age‘, 18]] [‘tom‘, [‘age‘, 18]] [‘anny‘, [‘age‘, 18]]

发现,虽然姓名都对号了,但是年龄却都变成了18.这是为什么呢?

我们看看它们元素的id

>>> [id(x) for x in jack]
[13463040, 13456608]
>>> [id(x) for x in tom]
[13463424, 13456608]
>>> [id(x) for x in anny]
[18501664, 13456608]

发现,其中列表中  姓名字符串  id都不一样,但是 年龄列表id却都相同。

这是因为:python中字符串不可以修改,所以在为tom和anny重新命名的时候,会重新创建一个’tom’和’anny’对象,替换旧的’jack’对象。

这就说明了,浅复制(shallow copy),它复制了对象,但对于对象中的元素,依然使用引用.

"""浅copy"""
import copy
aa = [1,2,3]
bb = copy.copy(aa)
print id(aa)    #11533088
print id(bb)    #12014776
bb[0] =100
print bb        #[100, 2, 3]
print aa        #[1,2,3]
#由于数字不可变,修改的时候会替换旧的对象
print [id(x) for x in bb]   #[10247196, 10246388, 10246376]
print [id(y) for y in aa]   #[10246400, 10246388, 10246376]

下面试试对象中可变元素:

lis = [[‘a‘],[1,2],[‘z‘,23]]
copyLis = copy.copy(lis)
copyLis[1].append(‘bar‘)
print copyLis   #[[‘a‘], [1, 2, ‘bar‘], [‘z‘, 23]]
print lis       #[[‘a‘], [1, 2, ‘bar‘], [‘z‘, 23]]

如果希望复制一个容器对象,以及它里面的所有元素(包含元素的子元素),使用copy.deepcopy,这个方法会消耗一些时间和空间,不过,如果你需要完全复制,这是唯一的方法.

"""深copy"""
deepLis = copy.deepcopy(lis)
deepLis[1].append(‘foo‘)
print deepLis   #[[‘a‘], [1, 2,‘foo‘], [‘z‘, 23]]
print lis       #[[‘a‘], [1, 2], [‘z‘, 23]]

注意:

1、对于非容器类型(如数字、字符串、和其他‘原子’类型的对象)没有被拷贝一说。

2、如果元祖变量只包含原子类型对象,则不能深copy。

3、对于python比较熟悉的人们都应该了解这个事实,在python中,strings, tuples, 和numbers是不可更改的对象,而list,dict等则是可以修改的对象。

4、我们刚才描述的浅拷贝和深拷贝操作都可以在copy模块中找到。其实copy模块中只有两个函数可用:copy()进行浅拷贝操作,而deepcopy()进行深拷贝操作。

时间: 2024-10-11 18:02:45

Python 浅拷贝和深拷贝的相关文章

python 浅拷贝与深拷贝

python变量在内存中是这样存储的: 在python中,一切都是对象,对象的存储是引用存储,存储的只是变量的值所在的内存地址,而不是这个变量的值本身. 如果对值进行修改,其实是在内存中新创建一个值,把变量指向这个值的地址 可以看出地址发生了改变 如果是两个值相等,那么改变其中一个不会影响另一个 a=b时a,b指向同一个地址 改变a的值后b不会改变 说明是在内存中新创建了一个值,a指向了这个值的地址 这是基本类型的存储 python中可以分两大类数据类型,一种是基础数据类型,如 int str

python浅拷贝和深拷贝

今天写兑换码时,玩家兑换兑换码时,拿到了上个兑换码的奖励,还一直怀疑,mysql取该兑换码那个环节出错了,实际上是直接引用了全局常量里的数据.导致后面全局常量的数据用的是上个兑换码的奖励内容. 今天就来说说前拷贝和深拷贝之分吧! 浅拷贝: copy.copy:拷贝内容 浅拷贝会生成一个新的对象,但是还是会使用原始数据的引用(地址),对可变类型操作会使用一个新的对象, 对不可变类型操作不会产生新的对象(list,dict,),并修改对应的值. 深拷贝: copy.deepcopy:完全拷贝内容 深

Python之美[从菜鸟到高手]--浅拷贝、深拷贝完全解读(copy源码分析)

可悲的我一直以为copy模块是用C写的,有时候需要深入了解deepcopy,文档描述的实在太简单,还是不知所云. 比如说最近看sqlmap源码中AttribDict的_deepcopy__有些疑惑, def __deepcopy__(self, memo): retVal = self.__class__() memo[id(self)] = retVal for attr in dir(self): if not attr.startswith('_'): value = getattr(se

python中关于赋值、浅拷贝与深拷贝的问题

Python中关于对象复制有三种类型的使用方式,赋值.浅拷贝与深拷贝.他们既有区别又有联系,刚好最近碰到这一类的问题,研究下. 一.赋值 在python中,对象的赋值就是简单的对象引用,这点和C++不同.如下: list_a = [1,2,3,"hello",["python","C++"]] list_b = list_a 这种情况下,list_b和list_a是一样的,他们指向同一片内存,list_b不过是list_a的别名,是引用. 我们可

一入python深似海--浅拷贝与深拷贝

python中有一个模块copy,deepcopy函数用于深拷贝,copy函数用于浅拷贝.要理解浅拷贝,必须先弄清楚python中的引用. 引用 Python中一切都是对象,变量中存放的是对象的引用.这是一个普遍的法则.可以说 Python 没有赋值,只有引用.如,a=1,变量a只是整数对象1的引用. 可变对象与不可变对象及其引用 一.不可变对象 不可变对象包括:数字,字符串,元组. 由于Python中的变量存放的是对象引用,所以对于不可变对象而言,尽管对象本身不可变,但变量的对象引用是可变的.

Python中赋值、浅拷贝与深拷贝

python中关于对象复制有三种类型的使用方式,赋值.浅拷贝与深拷贝.他们既有区别又有联系,刚好最近碰到这一类的问题,研究下. 一.赋值 在python中,对象的赋值就是简单的对象引用,这点和C++不同.如下: list_a = [1,2,3,"hello",["python","C++"]] list_b = list_a 这种情况下,list_b和list_a是一样的,他们指向同一片内存,list_b不过是list_a的别名,是引用. 我们可

Python中的赋值、浅拷贝、深拷贝

在理解浅拷贝和深拷贝之前,首先要理解学习一下变量在Python中是怎样存储的: 变量的类型是分值引用与地址引用两种. python的一切变量都是对象,变量的存储,采用了地址引用的方式,存储的只是一个变量的值所在的内存地址,而不是这个变量的只本身. 在Python中,是有多种数据类型:bool.int.long.float.string.list.dict.tuple.set; 其中可分为基本数据类型和复杂数据结构: 基本数据类型:bool.int.long.float.string; 复杂数据结

python中的深拷贝和浅拷贝理解

在python中,对象赋值实际上是对象的引用.当创建一个对象,然后把它赋给另一个变量的时候,python并没有拷贝这个对象,而只是拷贝了这个对象的引用.以下分两个思路来分别理解浅拷贝和深拷贝: 利用切片操作和工厂方法list方法拷贝 利用copy中的deepcopy方法进行拷贝 1.利用切片操作和工厂方法list方法拷贝 代码场景:有一个小伙jack,tom通过切片操作拷贝jack,anny通过工厂方法拷贝jack. >>> jack = ['jack', ['age', 20]] &g

Python(2.7.6) copy 浅拷贝与深拷贝

Python 标准库的 copy 模块提供了对象拷贝的功能. copy 模块中有两个函数 copy 和 deepcopy,分别支持浅拷贝与深拷贝. copy_demo.py import copy class MyClass(object): def __init__(self, name): super(MyClass, self).__init__() self.name = name a = [MyClass('huey')] b = copy.copy(a) c = copy.deepc