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. Python Flask打造一个视频网站实战视频教程

    下载链接:https://www.yinxiangit.com/607.html 目录: 本套课程从零基础讲解flask开发网站.涉及到的知识点包括:Python和pycharm的安装.urls和视图 ...

  2. JDK、Spring和Mybatis中使用到的设计模式

    一.JDK中的设计模式 (1)结构性模式 1.适配器模式 java.util.Arrays#asList() java.io.InputStreamReader(InputStream) java.i ...

  3. 泛型接口、JAVA API、包装类

    泛型接口就是拥有一个或多个类型参数的接口 语法: public interface 接口名<类型形参>{ 方法名(类型形参 类型形参实例); } 示例: public interface ...

  4. mgo操作mongodb

    mgo基本使用: http://labix.org/mgo 安装 # go get gopkg.in/mgo.v2 package main import ( "fmt" &quo ...

  5. C++11新增容器以及元组

    上次说了C++11的部分新特性,这里我们来说说新增的容器. unordered_map unordered_set unordered_multimap unordered_multiset arra ...

  6. [3]尝试用Unity3d制作一个王者荣耀(持续更新)->选择英雄-(中)

    如果已经看过本章节:目录传送门:这是目录鸭~ 上节内容写了Actor管理器,那么这一节让我们先创建一个角色.(此章节开始加速...) 1.制作角色展示AssetBundle: 提取农药某个展示模型(S ...

  7. ansible-playbook流程控制-loops循环使用

    1. ansible-playbook流程控制-loops循环使用    有时你想要多次重复任务.在计算机编程中,这称为循环.common ansible循环包括使用文件模块更改多个文件和/或目录的所 ...

  8. C# 缓存的实现

    缓存的实现 我们不是做第三方比如Redis等的缓存实现,而是根据实际情况,基于C#上做一些环境变量的保存,方便项目使用. 1.系统全局变量 很多时候,在系统运行开始,需要对系统的运行参数进行保存,以便 ...

  9. [C++]invalid initialization of non-const reference of type 'std::__cxx11::string& {aka std::__cxx11::basi

    解决方法:在参数前面加一个cosnt或者把引用符号去掉

  10. linux环境下Nginx的配置及使用

    切换到目录/usr/local/nginx/sbin,/usr/local为nginx的默认安装目录 #启动 ./nginx #查看命令帮助 ./nginx -h 验证配置文件状态 ./nginx - ...