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 --------------------------

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

  1. python中如何统计一个类的实例化对象

    类中的静态变量 需要通过类名.静态变量名 来修改 :通过对象不能修改 python中如何统计一个类的实例化对象?? class Person: #静态变量count,用于记录类被实例化的次数 coun ...

  2. js对象的直接赋值、浅拷贝与深拷贝

    最近Vue项目中写到一个业务,就是需要把对话框的表单中的数据,每次点击提交之后,就存进一个el-table表格中,待多次需要的表单数据都提交进表格之后,再将这个表格提交,实现多个表单数据的同时提交,期 ...

  3. 学习Python一年,这次终于弄懂了浅拷贝和深拷贝

    官方文档:copy主题 源代码: Lib/copy.py 话说,网上已经有很多关于Python浅拷贝和深拷贝的文章了,不过好多文章看起来还是决定似懂非懂,所以决定用自己的理解来写出这样一篇文章. 当别 ...

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

    对象的扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中. 拷贝对象 let aa = { age: 18, name: 'aaa' } let bb = {...aa}; co ...

  5. python中的函数的参数和可变参数

    最近在搞python的过程中需要用到给函数传可变参数..所以去网上找前人的帖子学习了一下 为了尊重原作者,这里附上链接:http://www.cnblogs.com/tqsummer/archive/ ...

  6. python中表示False的一些内置对象

    By default, an object is considered true unless its class defines either a __bool__() method that re ...

  7. Python里面如何拷贝一个对象?(赋值,浅拷贝,深拷贝的区别)

    答:赋值(=),就是创建了对象的一个新的引用,修改其中任意一个变量都会影响到另一个. 浅拷贝:创建一个新的对象,但它包含的是对原始对象中包含项的引用(如果用引用的方式修改其中一个对象,另外一个也会修改 ...

  8. 《python解释器源码剖析》第6章--python中的dict对象

    6.0 序 元素和元素之间可能存在着某种关系,比如学生姓名和成绩.我希望能够通过学生的姓名找到这个学生的成绩,那么只需要将两者关联起来即可.字典正是这么做的,字典中的每个元素就是一个key:value ...

  9. Python探索记(16)——Python的可变类型与不可变类型

    # @Time : 2017/7/8 17:49 # @Author : 原创作者:谷哥的小弟 # @Site : 博客地址:http://blog.csdn.net/lfdfhl # @DESC : ...

  10. 【Python核心编程笔记】一、Python中一切皆对象

    Python中一切皆对象 本章节首先对比静态语言以及动态语言,然后介绍 python 中最底层也是面向对象最重要的几个概念-object.type和class之间的关系,以此来引出在python如何做 ...

随机推荐

  1. Centos7 C++ 安装使用googletest单元测试

    废话不多说,直接开始吧. 环境说明 系统环境:centos7.0 g++ 版本: g++ (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36) 查看方法: g++ -vers ...

  2. Java中的java.lang.Class API 详解

    且将新火试新茶,诗酒趁年华. 概述 Class是一个位于java.lang包下面的一个类,在Java中每个类实例都有对应的Class对象.类对象是由Java虚拟机(JVM)自动构造的. Class类的 ...

  3. springmvc项目中的中文乱码的解决及未生效解决

    情景: springmvc项目中,在控制台输出时中文乱码,在web网页中正常. 解决方法: 在web.xml中添加如下代码: <!-- 中文乱码解决 --> <filter> ...

  4. 如何部署 H5 游戏到云服务器?

    在自学游戏开发的路上,最有成就感的时刻就是将自己的小游戏做出来分享给朋友试玩,原生的游戏开可以打包分享,小游戏上线流程又长,那 H5 小游戏该怎么分享呢?本文就带大家通过 nginx 将构建好的 H5 ...

  5. 360大牛 全面解读 PHP面试

    360大牛全面解读PHP面试 第1章 课程介绍 让大家了解基本面试流程和面试的核心要求以及意义是什么并理解PHP面试考点主要以基础为核心,说明PHP面试考察范围. 第2章 PHP基础知识考察点 本章主 ...

  6. Docker在IDEA中的使用以及如何部署到服务器

    IDEA中实现一键部署到服务器 点击运行自动部署到服务器: 服务器上安装docker 1,添加yum源 # yum install epel-release –y# yum clean all# yu ...

  7. PopUpWindow 的使用笔记

    最接做需求的时候,碰到了 PopUpWindow,但是也没做过多了解,就是照搬别人的代码改改逻辑.后面视觉看了之后,说让我加一些动画效果,使用起来更加舒服.可是我看别人以前也没有写,于是就开始捣鼓 P ...

  8. java、if判断和循环

    一.选择.循环语法    选择        if            if(表达式)语句A:                如果表达式的值是真的,就会执行语句A,否则不执行             ...

  9. 第一次登陆jenkins页面空白解决方案

    之前搭建了几次jenkins环境都没问题,最近换了工作,再次搭建jenkins用的是docker部署: https://www.cnblogs.com/yy-cola/p/10457484.html ...

  10. 第四周 Java课件内容动手动脑

    1.JDK中的Math类 package ke1; public class TestMath { public static void main(String[] args) { /*------- ...