Python中的可变对象与不可变对象、浅拷贝与深拷贝

Python中的对象分为可变与不可变,有必要了解一下,这会影响到python对象的赋值与拷贝。而拷贝也有深浅之别。

不可变对象

简单说就是某个对象存放在内存中,这块内存中的值是不能改变的,变量指向这块内存,如果要改变变量的值,只能再开辟一块内存,放入新值,再让变量指向新开辟的内存。

#定义三个变量
f=22
n=22
z=f
print(‘f=%s,n=%s,z=%s‘ %(f,n,z))
print(‘f的地址:‘,id(f))#id用于获取变量内存地址
print(‘n的地址:‘,id(n))
print(‘z的地址:‘,id(z))
print(‘注意:f、n、z的地址是一样的。\n‘)
n=9  #改变n的值
z=6  #改变z的值
print(‘f=%s,n=%s,z=%s‘ %(f,n,z))
print(‘f的地址:‘,id(f))
print(‘n的地址:‘,id(n))
print(‘z的地址:‘,id(z))
print(‘注意:f、n、z的地址不一样了。‘)

执行结果:

f=22,n=22,z=22
f的地址:8790949926368
n的地址:8790949926368
z的地址:8790949926368
注意:f、n、z的地址是一样的。

f=22,n=9,z=6
f的地址:8790949926368
n的地址:8790949925952
z的地址:8790949925856
注意:f、n、z的地址不一样了。

上面的例子可以看出,当变量的值改变,它的地址也跟着改变了,就证明当变量的值改变时,python并没有将原地址空间的内容改变,而是又重新分配了一块内存空间,放上新值,然后将变量指向新开辟的空间地址。对于赋值,可以看出将 f 的值赋给 z,它们的地址是一样的,但是当改变 z 的值,z的地址也跟着改变了,f 的值并没有改变,因为它们的地址不一样了。

 可变对象

就是变量所指向的内存中的值是可以改变的。如果要修改变量值,不用开辟新的内存空间,直接在原地改变值,变量的地址不会改变。

下面的例子创建了两个 list,值是相同的,打印地址可以看出它们的地址是不一样的。再创建一个s3,将s1的值赋给s3,然后改变s3的值,发现s1的值也跟着变了,因为它们的指向的地址空间是一样。

s1=[3,6,9]
s2=[3,6,9]
print(s1,s2)
print(‘s1的地址:%s,s2的地址:%s‘ %(id(s1),id(s2)))
#可以看出s1和s2的地址是不一样的,虽然值一样。
s3=s1#将s1的值赋给s3
print(‘s3的地址:%s‘ %(id(s3)))#s1和s3的地址是一样的
s3.append(11)#在s3里面添加一个元素
print(s1,s3)#s1的值也跟着改变了

执行结果:

执行结果:
[3, 6, 9] [3, 6, 9]
s1的地址:92282952,s2的地址:92283976
s3的地址:92282952
[3, 6, 9, 11] [3, 6, 9, 11]

在python中,

  • 数值类型(int 和 float)、字符串、元组都是不可变类型。
  • 列表、字典、集合是可变类型。

浅拷贝与深拷贝

拷贝表面看就是复制一个一样的对象,但它们最本质的区别就是复制出来的这个对象的地址是否和原对象的地址一样,就是在内存中存放这两个对象的位置是否发生了改变。由浅入深可分为三个层次(直接赋值、浅拷贝、深拷贝)。

以一个list为例子,演示三种不同层次的拷贝:首先构造一个list,这个list里面有两种不同类型的元素,分别为不可变元素1,2,3和可变元素[‘FF‘,‘WW‘],即ff=[1,2,3,[‘FF‘,‘WW‘]]。通过分析list对象即其里面包含元素的地址是否改变可以清晰看出拷贝的深浅之别。

第一层:直接赋值:构造一个 nn,直接将 ff 赋值给 nn,这种情况下 ff 和 nn 以及它们的元素所指向的地址是一样的,改变任何一个对象,另一个也一样改变,因为这个两个对象及其元素指向的内存空间是一样的,它们没有自己的独立内存空间。

#浅拷贝和深拷贝:直接赋值
ff=[1,2,3,[‘FF‘,‘WW‘]]
nn=ff  #将ff直接赋值给nn
print(‘ff的地址:%s,ff[3]的地址:%s‘ %(id(ff),id(ff[3])))
print(‘nn的地址:%s,nn[3]的地址:%s‘ %(id(nn),id(nn[3])))
nn.append(4)#修改nn,在后面添加一个元素
nn[0]=99#修改nn元素值
nn[3].append(‘NN‘)#修改nn,在第三个list元素里添加一个元素
print(nn)#nn变了
print(ff)#ff也跟着变了

执行结果:

ff的地址:95127176,ff[3]的地址:95277576
nn的地址:95127176,nn[3]的地址:95277576
[99, 2, 3, [‘FF‘, ‘WW‘, ‘NN‘], 4]
[99, 2, 3, [‘FF‘, ‘WW‘, ‘NN‘], 4]

第二层:浅拷贝:使用语句nn=copy.copy(ff)完成拷贝,通过观察变量地址的变化来理解拷贝的不同。打印地址发现对象 nn 和原对象 ff 地址已经不一样了,但元素的地址是一样的,然后对 nn 做一下改变。

  1. 为nn添加一个元素,ff与nn地址不同,ff没有受到影响。
  2. 将nn[1]的值改变,nn[1]为数值,是不可变对象,改变就是新开辟了内存。nn[1]的地址发生了变化,ff[1]的地址没有改变,所以ff[1]的值没有发生变化。
  3. 将nn[3]的值改变,nn[3]为列表,是可变对象,nn[3]的值变地址没有变,因为ff[3]和nn[3]的地址相同,所以ff[3]的值也就改变了。

可见,浅拷贝复制的是元素的地址引用,如果元素是不可变类型,修改就更新了地址,和原对象的地址不同了,所以原对象不会受到影响,当元素是可变类型,修改没有改变地址,这样原对象也就跟着变化。

跟着变化。
#浅拷贝和深拷贝:浅拷贝
import copy
ff=[1,2,3,[‘FF‘,‘WW‘]]
nn=copy.copy(ff)#浅拷贝
print(‘ff的地址:‘,id(ff))
print(‘ff[1]的地址:‘,id(ff[1]))#ff里的不可变元素
print(‘ff[3]的地址:‘,id(ff[3]),‘\n‘)#ff里的可变元素

print(‘nn的地址:‘,id(nn))
print(‘nn[1]的地址:‘,id(nn[1]))#nn里的不可变元素
print(‘nn[3]的地址:‘,id(nn[3]),‘\n‘)#nn里的可变元素

nn.append(4)#修改nn,在后面添加一个元素
nn[1]=55 #修改不可变元素的值
nn[3].append(‘NN‘) #修改可变元素的值
print(‘ff:‘,ff)
print(‘nn:‘,nn,‘\n‘)
print(‘ff的地址:%s,ff[1]的地址:%s,ff[3]的地址:%s‘ %(id(ff),id(ff[1]),id(ff[3])))
print(‘nn的地址:%s,nn[1]的地址:%s,nn[3]的地址:%s‘ %(id(nn),id(nn[1]),id(nn[3])))

执行结果:

ff的地址:96794888
ff[1]的地址:8790949925728
ff[3]的地址:96796168

nn的地址:96796040
nn[1]的地址:8790949925728
nn[3]的地址:96796168

ff: [1, 2, 3, [‘FF‘, ‘WW‘, ‘NN‘]]
nn: [1, 55, 3, [‘FF‘, ‘WW‘, ‘NN‘], 4] 

ff的地址:96794888,ff[1]的地址:8790949925728,ff[3]的地址:96796168
nn的地址:96796040,nn[1]的地址:8790949927424,nn[3]的地址:96796168

第三层:深拷贝:改变任何一个对象都对另一个没有影响,它们是独立的。通过观察地址可以看出,对于不可变元素,重新创建一份似乎有点多余,反正修改就会刷新地址,所以ff[1]和nn[1]的地址还是一样的,主要看可变对象ff[3]和nn[3],深拷贝后它们的地址已经不一样了。

#浅拷贝和深拷贝:深拷贝
import copy
ff=[1,2,3,[‘FF‘,‘WW‘]]
nn=copy.deepcopy(ff)#深拷贝
print(‘ff的地址:‘,id(ff))
print(‘ff[1]的地址:‘,id(ff[1]))#ff里的不可变元素
print(‘ff[3]的地址:‘,id(ff[3]),‘\n‘)#ff里的可变元素

print(‘nn的地址:‘,id(nn))
print(‘nn[1]的地址:‘,id(nn[1]))#nn里的不可变元素
print(‘nn[3]的地址:‘,id(nn[3]),‘\n‘)#nn里的可变元素

nn.append(4)  #修改nn,在后面添加一个元素
nn[1]=55  #修改不可变元素的值
nn[3].append(‘NN‘) #修改可变元素的值
print(‘ff:‘,ff)
print(‘nn:‘,nn,‘\n‘)
print(‘ff的地址:%s,ff[1]的地址:%s,ff[3]的地址:%s‘ %(id(ff),id(ff[1]),id(ff[3])))
print(‘nn的地址:%s,nn[1]的地址:%s,nn[3]的地址:%s‘ %(id(nn),id(nn[1]),id(nn[3])))

执行结果:

ff的地址:93244616
ff[1]的地址:8791030272864
ff[3]的地址:93241416

nn的地址:93244552
nn[1]的地址:8791030272864
nn[3]的地址:93244744

ff: [1, 2, 3, [‘FF‘, ‘WW‘]]
nn: [1, 55, 3, [‘FF‘, ‘WW‘, ‘NN‘], 4] 

ff的地址:93244616,ff[1]的地址:8791030272864,ff[3]的地址:93241416
nn的地址:93244552,nn[1]的地址:8791030274560,nn[3]的地址:93244744

通俗一点:

  • 直接赋值:两个对象你中有我,我中有你,同住一间房。
  • 浅拷贝:两个对象分居中,没离婚,藕断丝连,有部分个人空间。
  • 深拷贝:两个对象彻底离婚了,都不在一个城市了,各住各家,互不影响。

-------------------------- END --------------------------

原文地址:https://www.cnblogs.com/nnzhang/p/11708057.html

时间: 2024-08-29 21:37:18

Python中的可变对象与不可变对象、浅拷贝与深拷贝的相关文章

探寻Python中如何同时迭代多个iterable对象

题外话: 最近因为课程需要开始深入了解Python语言.因为以前一直用的Java.C++等强类型的静态语言,现在突然使用Python确实感受到了很大的不同. 直观感觉就是,在Python中总是能找到一些让代码变得精巧.简洁.高效.美观的写法,使得初学者在写代码的过程充满了惊喜,从而渐渐喜欢上Python.而且Python的官方手册阅读起来感觉非常好,很多问题都描述的很清楚.不过总体来说,还是觉得Java大法好:) Python中一个非常有用的语法就是for in循环跟iterable对象的结合,

python中通过元类(TYPE)简单实现对象关系映射(ORM)

ORM是创建一个实例对象,用创建他的类名当做数据表名,用创建他的类属性对应数据表的字段,不需要在自己写复杂的sql语句,而是通过对实例对象的操作时,能让代码自动帮我们整理为对应的sql语句. class User(父类): uid = ("uid", "int unsigned") name = ("username", "varchar(20)") password = ("password", &quo

ES6 对象解构赋值(浅拷贝 VS 深拷贝)

对象的扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中. 拷贝对象 let aa = { age: 18, name: 'aaa' } let bb = {...aa}; console.log(bb); // {age: 18, name: "aaa"} 合并对象 扩展运算符(...)可以用于合并两个对象 let aa = { age: 18, name: 'aaa' } let bb = { sex: '男' } let cc = {...aa, ...bb

Python中函数的参数定义和可变参数

转自:http://www.cnblogs.com/tqsummer/archive/2011/01/25/1944416.html  简洁易懂啊,好文 刚学用Python的时候,特别是看一些库的源码时,经常会看到func(*args, **kwargs)这样的函数定义,这个*和**让人有点费解.其实只要把函数参数定义搞清楚了,就不难理解了. 先说说函数定义,我们都知道,下面的代码定义了一个函数funcA def funcA():  pass    显然,函数funcA没有参数(同时啥也不干:D

react中修改参数值没有重新渲染问题-关于浅拷贝与深拷贝

项目中遇到个问题,有一个数组参数,删除数组中一个元素,并且重新改变state但是页面没有重新渲染,先面用一个简单的todolist重现下问题 如下渲染出来menu,当我点击删除时触发onClose事件,在onClose中直接对listData进行操作,打印出的数据是删除后的,但是页面没有重新渲染 const data=[ {name:'小A',age:'10',id:'1'}, {name:'小B',age:'11',id:'2'}, {name:'小C',age:'10',id:'3'}, {

使用C语言为python编写动态模块(1)--从底层深度解析python中的对象以及变量

楔子 我们知道可以通过使用C语言编写动态链接库的方式来给python加速,但是方式是通过ctypes来加载,通过类CDLL将动态链接库加载进来得到一个对象之后,通过这个对象来调用动态链接库里面的函数.那么问题来了,我们可不可以使用C语言为python编写模块呢?然后在使用的时候不使用ctypes加载动态库的方式,而是通过python的关键字import进行加载. 答案是可以的,我们知道可以通过编写py文件的方式来得到一个模块,那么也可以使用C语言来编写C源文件,然后再通过python解释器进行编

python中对象初始化放在内存中什么位置

截屏自<Python源码剖析>正文第一页. 也即: 当在其他class中使用 时,node是在堆中的.相当于在C中调用malloc. 不仅仅是对于像Node这样的class 的对象是存在于堆中的,在Python中就连int类型的数值同样是存在在堆中的.因为在Python中就连基本数据类型同样是对象. 让我们思考下Node中的value和self.value这两个变量: 为什么在Node中其他的方法能够调用self.value而不能够调用value? 以前我使用C/C++的思维方法来理解这个问题

Python中的几种数据类型

大体上把Python中的数据类型分为如下几类: Number(数字)                  包括int,long,float,complex String(字符串)                例如:hello,"hello",hello List(列表)                    例如:[1,2,3],[1,2,3,[1,2,3],4] Dictionary(字典)              例如:{1:"nihao",2:"h

python中修改字符串的值

demo: info = 'abc' 如果要把上面的字符串info里面的c替换成d,要怎么操作呢? 方法一:使用python中的replace()方法 语法: str.replace(old, new[, max]) 参数: old -- 将被替换的子字符串. new -- 新字符串,用于替换old子字符串. max -- 可选字符串, 替换不超过 max 次 >>> info = "abc" >>> str = info.replace("

python中的oop面向对象基本编程(类、实例、魔术)

                  OOP面向对象(老师说这玩意蕴含基本哲学哦!!) 面向对象编程--Object Oriented Programming,简称OOP,是一种程序设计思想.OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数.数据封装.继承和多态是面向对象的三大特点. 面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行.为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度. 而面向对象的程序设计把