python中变量中储存的是什么_【python】python中变量存储及内存管理形式

这篇文章主要是对python中的数据进行认识,对于很多初学者来讲,其实数据的认识是最重要的,也是最容易出错的。本文结合数据与内存形态讲解python中的数据,内容包括:

引用与对象

可变数据类型与不可变数据类型

引用传递与值传递

深拷贝与浅拷贝

python内存管理机制

1、引用与对象

引用与对象的关系

1335962-20190802170419171-404450971.jpg

name1 = 'Jim'

name2 = ‘Tom’

内存

name1------> 'Jim'

name2------>'Tom'

对象:当创建数据对象时,在内存中会保存对象的值,这个值就是对象自己;

引用:对象保存在内存空间,外部想要使用对象的值,需要使用引用,就是'name1', 'name2'。内存会保存对象的引用计数,当某个对象的引用数量为0时,对象会被回收。

2、可变数据类型和不可变数据类型

可变与不可变:是指内存中那块内存的存的value是否可以被改变。如果是不可变类型,在对象本身操作的时候,必须在内存中新申请一块区域(因为老区域不可变),如果是可变类型,对象在操作的时候,不需要再申请其它地方内存

(1) python中不可变的数据类型,不允许变量的值发生变化,如果改变了变量的值,相当于是新建了一个对象,而对于相同的值的对象,在内存中则只有一个对象,内部会有一 个引用计数来记录有多少个变量引用这个对象

(2)可变数据类型,允许变量的值发生变化,即如果对变量进行append、+ =这种操作后,只是改变了变量的值,而不会新建一个对象,变量引用的对象的地址也不会发生变化,不过对于相同的值不同的对象,在内存中则会存在不同的对象,即每个对象都有自己的地址,相当于内存中对于同值的对象保存了多份,这里不存在引用计数,是是实实在在的对象。

(3)不可变数据类型:不可变是指对象本身的值是不可变的(当你创建a = 1整型对象,用a去引用它,内存中的对象1是不变的,当执行a = 2 时,只是重新创建了对象2,用a引用,如果1对象没有其它引用会被回收)

ContractedBlock.gif

ExpandedBlockStart.gif

print('-------------可变类型数据测试----------------')

a= [1,2,3]

b= [1,2,3]

cp_a=aprint(id(a)) #输出:45460360

print(id(b)) #输出:45472584

print(a == b) #输出:True

print(a is b) #输出:False

a[0] = 2

print(id(a)) #输出:45460360

print(cp_a) #输出:[2,2,3]

View Code

3、引用传递与值传递

可变对象为引用传递,不可变对象为值传递。

1、当传递列表或者字典等可变对象时,如果改变引用的值,就修改了原始对象

ContractedBlock.gif

ExpandedBlockStart.gif

defpass_cite(lis):

lis.append('add_one')print(lis)

lis= [1,2,3]

pass_cite(lis)#输出: [1,2,3,'add_one']

print(lis) #输出: [1,2,3,'add_one']

View Code

2、当传递列表为不可变对象时,则只是创建了不同的对象,原始对象并没有变

如下面实例:

ContractedBlock.gif

ExpandedBlockStart.gif

defpass_value(num):print('inner in the function:', id(num)) #输出 inner in the function: 1717892656

num = num +10

print('inner after plus:', id(num)) #输出(创建新的对象) inner after plus: 1717892976

num= 100

print('outer num :', id(num)) #输出 outer num : 1717892656

pass_value(num) #输出 inner in the function: 1717892656 / 创建新的对象) inner after plus: 1717892976

print('outer after fun:', id(num)) #输出 outer after fun: 1717892656

View Code

4、深拷贝与浅拷贝

相对于其他传统编程语言,Python有一个比较奇怪的特性,即在复制对象时,有浅拷贝(shallow copy)和深拷贝(deep copy)两种方式。

浅拷贝和深拷贝只和复合对象相关。复合对象指的是包含对象的对象,如列表(list)、类实例(class instance)等。简单类型的对象(int、float、string等)不存在浅拷贝和深拷 贝的说法。

ContractedBlock.gif

ExpandedBlockStart.gif

color1 = ['red', 'blue']

color2=color1print(id(color1), id(color2)) #输出45341640 45341640

print(color1 is color2) #输出 True

View Code

再看给color2赋值,会出现什么情况?

ContractedBlock.gif

ExpandedBlockStart.gif

color1 = ['red', 'blue']

color2=color1print(id(color1), id(color2)) #输出45341640 45341640

print(color1 is color2) #输出 True

color2= ['rouge','vert']print(color1) #输出 ['red', 'blue']

print(color2) #输出 ['rouge', 'vert']

print(id(color1), id(color2)) #输出 45472712 45460040

View Code

跟我们期望的一样,colours1的值保持不变,一个新的内存地址被分配给了colours2。

ContractedBlock.gif

ExpandedBlockStart.gif

color1 = ['red', 'blue']

color2=color1print(id(color1), id(color2)) #输出45472712 45472712

color2[1] = 'green'

print(color1) #输出 ['red', 'green']

print(color2) #输出 ['red', 'green']

print(id(color1), id(color2)) #输出 45472712 45472712

View Code

可以看到,当我们将colours2中的第二个元素重新赋值时,colours1中的值也被自动改变了,很多初学者在这里都非常迷惑。事实上我们并没有分配一个新的对象给colours2。colours1和colours2仍然指向同一个列表对象。即我们没有两个列表,仍然只有1个,只不过有2个名字。

那对于简单列表,有没有完全拷贝的方案呢,有!那就是使用切片方法。因为切片方法是重新生成了一个新对象。

ContractedBlock.gif

ExpandedBlockStart.gif

lis1 = ['a','b','c','d']

lis2=lis1[:]

lis2[1] = 'x'

print(lis1) #输出['a', 'b', 'c', 'd']

print(lis2) #输出['a', 'x', 'c', 'd']

View Code

但是,如果是像下面这样的嵌套列表,就又会遇到新的问题。因为切片操作本质上仍然是浅拷贝。当遇到嵌套列表时,切片方法只复制子列表的地址,而不是其全部内容。

ContractedBlock.gif

ExpandedBlockStart.gif

lst1 = ['a', 'b', ['ab', 'ba']]

lst2=lst1[:]

lst2[0]= 'c'

print(lst1) #输出['a', 'b', ['ab', 'ba']]

print(lst2) #输出['c', 'b', ['ab', 'ba']]#--------------------------------------

lst2[2][1] = 'd'

print(lst1) #输出['a', 'b', ['ab', 'd']]

print(lst2) #输出['c', 'b', ['ab', 'd']]

View Code

下面的图给出了lst1和lst2的数据结构描述,lst2虽然是一个新建对象,但其中的子列表[‘ab’,’ba’]与lst1中的指的是同一个对象。

1335962-20190805104558241-719962620.png

上面代码对其进行修改示意图如下:

1335962-20190805105055872-377178549.png

1335962-20190805105121838-446943321.png

一个解决方案是使用标准库的copy模块。如果我们需要让一个对象发生改变时不对原对象产生副作用,就需要一份这个对象的深度拷贝。深拷贝不仅仅拷贝了原始对象自身,也对其包含的值进行拷贝,它会递归的查找对象中包含的其他对象的引用,来完成更深层次拷贝。因此,深拷贝产生的副本可以随意修改而不需要担心会引起源对象的改变。

对先前的例子使用深拷贝:

ContractedBlock.gif

ExpandedBlockStart.gif

from copy importdeepcopy

lst1= ['a', 'b', ['ab', 'ba']]

lst2=deepcopy(lst1)print(lst1) #输出 ['a', 'b', ['ab', 'ba']]

print(lst2) #输出 ['a', 'b', ['ab', 'ba']]

print(id(lst1)) #输出 45605320

print(id(lst2)) #输出 45907080

print(id(lst1[0])) #输出 38229024

print(id(lst2[0])) #输出 38229024

print(id(lst1[2])) #输出 45605448

print(id(lst2[2])) #输出 45907016

View Code

可以看出lst1和lst2的嵌套子列表的内存地址不一样了。下面的图给出了deepcopy后的数据结构情形:

1335962-20190805112128993-112474067.png

1335962-20190805112249411-682567450.png

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值