python变量与内存

首先声明本文所有测试结果由vs2019 community版,python3.8实现,
小白多方参考,新手司机上路,难免有错漏。考虑到不同解释器可能做出的优化或py版本相异的原因造成的结果不同,请以自身结果为准。如有疑问,请在评论区附上py版本并指出

每个python对象都有三个重要的属性:

  1. 编号(内存地址)
  2. 类型

关于编号:
一个对象被创建后,它的 编号 就绝不会改变。你可以将其理解为该对象在内存中的地址
‘is’ 运算符可以比较两个对象的编号是否相同
id() 函数能返回一个代表其编号的整型数
不要急,我们将在下文一一讲解

python语言的引用

对于python而言,python的一切变量都是对象,变量的存储,采用了引用语义的方式,存储的只是一个对象的内容所在的内存地址,而不是这个变量的值本身

引用、id与赋值

变量的每一次初始化,都开辟了一个新的空间,将新内容的地址通过 “=” 赋值给变量,这个过程称为引用

在这里插入图片描述

不同于C语言。每个python变量所需的存储空间大小一致,因为变量只是保存了一个引用。图中的每一根红箭头的指向都是一个引用的指向。
变量内存
内置函数 id() 返回的是一个代表其编号的整型数,可以理解为返回的是对象的身份标识 ,但本篇探讨重点不在于此,实际上这需要更底层的研究:
Python中的id()探讨

另外,我们可以用 is 来直接判断变量间是否引用的是同一个对象

#现在来简单测试一下
a=1
b=1;print(a is b,id(a),id(b))
c=1.0
d=1.0;print(c is d,id(c),id(d))
e=[1]
f=[1];print(e is f,id(e),id(f))
g='hello world,python program is good'
h='hello world,python program is good';print(g is h,id(g),id(h))
i=(1,2)
j=(1,2);print(i is j,id(i),id(j))
#第一轮测试
True 2042230704 2042230704
True 10633040 10633040
False 11194152 11376424
True 11167264 11167264
True 11191176 11191176
#第二轮测试
True 2042230704 2042230704
True 10501968 10501968
False 47107880 48011048
True 47080992 47080992
True 47104904 47104904
#第三轮测试
True 2042230704 2042230704
True 15089488 15089488
False 24432424 52008744
True 24405536 24405536
True 24429448 24429448

为什么会有False的情况,我们将在下文探讨对象的可变与不可变

本节补充: 在多轮测试中整数 1 的 id 值没有发生变化 这是因为Python为了优化速度,使用了小整数对象池,[-5 ~ 256] 的整数,
避免为整数频繁申请和销毁内存空间,所采取重用对象内存的办法

关于is 和“==”:

is 比较的是两个实例对象是不是完全相同,它们是不是同一个对象,占用的内存地址是否相同。

is ==
1.内容相同 2.内存中地址相同 两个对象的“值“”是否相等

python的垃圾处理机制

引用计数器是主要的python垃圾回收机制
每个python对象都有引用计数器用来记录当前对象被引用的次数,也就是来追踪到底有多少引用指向了这个对象,当指向该对象的内存的引用计数器为0的时候,该内存将会被Python虚拟机销毁释放
当发生以下四种情况的时候,对象 a 的引用计数器+1

  1. 对象被创建:  a=14
  2. 对象被引用:  b=a
  3. 对象被作为参数,传到函数中:   func(a)
  4. 对象作为一个元素,存储在容器中:   List={a,”a”,”b”,2}

与上述情况相对应,当发生以下四种情况时,该对象的引用计数器-1

  1. 当该对象的别名被显式销毁时:  del a
  2. 当该对象的引别名被赋予新的对象:  a=26
  3. 一个对象离开它的作用域,一个函数执行完毕时,函数里面的局部变量的引用计数器就会减1(但是全局变量不会)
  4. 将该元素从容器中删除时,或者容器被销毁时。

不可变对象和可变对象

应该是分别用可哈希数据不可哈希数据称呼,我们用对象是否可变以便于理解
区别就在于:
不可变变量首先引用的地址指向是具体的值:整型、浮点型、字符串和元组
可变变量首先引用的地址(即首地址)指向是一个容器(这个容器可以存储很多地址,指向值、容器等):列表、集合、字典

不可变对象(可哈希数据)

整型、浮点型、字符串和元组
不允许变量发生变化,如果改变了变量,相当于是新建了一个对象;对于相同的值的对象,在内存中则只会存在一个。
所谓改变变量的值,其实是引用了不同的对象

在这里插入图片描述

#我们对小整数、浮点数、字符串(单个)、长字符串、元组测试
a=1
b=1;print(a is b,id(a),id(b))
c=1.0
d=1.0;print(c is d,id(c),id(d))
e='1'
f='1';print(e is f,id(e),id(f))
g='hello world,python program is good'
h='hello world,python program is good';print(g is h,id(g),id(h))
i=(1,2)
j=(1,2);print(i is j,id(i),id(j))
#测试结果(True表明变量引用的是不可变对象)
True 2042230704 2042230704 
True 24723280 24723280
True 25200992 25200992
True 25274032 25274032
True 14336904 14336904

此时我们发现变量引用对象相同,id值相同

可变对象(不可哈希数据)

列表、集合、字典
变量首先引用的地址指向是一堆地址(由元素存储),这些地址又分别指向下一级,以此类推,直到指向的是 不可变对象(值)

list1=[1,[1]]
list1[0] #元素在第1层,储存的地址指向第2层,为不可变对象 1
list1[1] #元素在第1层,储存的地址指向第2层,为指向下一层的地址 
list1[1][0] #元素在第2层,储存的地址指向第3层,为不可变对象 1

如果对变量进行append、+=等这种操作后,只是改变了对象的内部,而不会新建一个对象,因为变量引用的对象的地址(首地址)没有改变,只是对应地址的内容改变或者地址发生了扩充,所以对于相同的值的不同对象,内存中允许存在多份,即每个对象都有自己的地址
改变变量内部元素的值,其实是改变了对象内部元素的地址指向

#我们对列表、集合、字典
a=[1,2,[3]]
b=[1,2,[3]];print(a is b,id(a),id(b))
c={1,2,3}
d={1,2,3};print(c is d,id(c),id(d))
e={'q':1,'w':1,'r':1}
f={'q':1,'w':1,'r':1};print(e is f,id(e),id(f))

print(id(a[0]),id(b[0]),id(a[2]),id(b[2]))
#测试结果(True表明变量引用的是不可变对象)
False 28888872 58038056
False 58078120 58078008
False 28878480 28878560
2042230704 2042230704 14143272 14326376

此时我们发现变量引用对象不相同,id值不相同。但是对于元素是不可变对象的,id值仍然相同

我们以列表对象为例:

在这里插入图片描述

如果修改其中某一项元素的值,或者添加几个元素,不会改变其列表本身的地址,只会改变其内部元素的地址引用

在这里插入图片描述
在这里插入图片描述
但是如果对其进行重新赋值 “=” 操作时,就会给列表重新赋予一个地址,来覆盖之前的地址,这时列表地址会发生改变

a=[1,2]
print(id(a))  #49270568
a=[1,2]
print(id(a))  #50173736

赋值、浅拷贝与深拷贝

赋值
我们经常用到各种赋值运算符:
对于不可变对象:“=”与“+=”、“*=”、“/=”最后结果都是创建新对象
在这里插入图片描述

但对于可变对象我们需要注意区分“=”与“+=”、“*=”、“/=”
注意:此时 += 不会创建新对象,而 + 后再 = 会创建新对象

a = [1,2]
print(id(a))
#47952136
a += [3,4]
print(id(a))
#47952136  结果前后一致
print(a)
#[1, 2, 3, 4]
b = [1,2]
print(id(b))
#48147208
b = b + [3,4]
print(id(b))
#48062984  不一致
print(b)
#[1, 2, 3, 4]

浅拷贝:不拷贝子对象的内容,只是拷贝第一层子对象的引用

在这里插入图片描述
深拷贝:会连子对象的内存也全部拷贝一份(一直追溯到最深处),对子对象的修改不会影响源对象
在这里插入图片描述
我们现在循序渐进的深入对比一下各种 “拷贝”
1、赋值

alist=[1, 2, 3, ['a', 'b']]
b=alist
print(b)
#[1, 2, 3, ['a', 'b']]
alist.append(5)
print(alist);print(b)
#[1, 2, 3, ['a', 'b'], 5] 
#[1, 2, 3, ['a', 'b'], 5] #对象随之改变

赋值 “拷贝” 只是引用

2、浅拷贝

import copy
alist=[1, 2, 3, ['a', 'b']]
c=copy.copy(alist)
print (alist);print(c)
#[1, 2, 3, ['a', 'b']]
#[1, 2, 3, ['a', 'b']]
alist.append(5)
print(alist);print(c)
#[1, 2, 3, ['a', 'b'], 5]
#[1, 2, 3, ['a', 'b']] #不可变对象没有随之改变
alist[3]
#['a', 'b']
alist[3].append('cccc')
print(alist);print(c)
#[1, 2, 3, ['a', 'b', 'cccc'], 5]
#[1, 2, 3, ['a', 'b', 'cccc']]  #可变对象随之发生改变

第一层包含的不可变对象被拷贝的是值,可变对象拷贝的是地址(指向下一层)

3、深拷贝

import copy
alist=[1, 2, 3, ['a', 'b']]
d=copy.deepcopy(alist)
print (alist);print(d)
#[1, 2, 3, ['a', 'b']]
#[1, 2, 3, ['a', 'b']]
alist.append(5)
print (alist);print(d)
#[1, 2, 3, ['a', 'b'], 5]
#[1, 2, 3, ['a', 'b']] #不可变对象没有随之改变
alist[3]
#['a', 'b']
alist[3].append("ccccc")
print (alist);print(d)
#[1, 2, 3, ['a', 'b', 'ccccc'], 5]
#[1, 2, 3, ['a', 'b']]  #可变对象没有随之改变

深拷贝实现了完全意义上的拷贝(创建一个互不干扰,真正独立的副本)

常见浅拷贝:

  1. 使用切片[:]操作
  2. 使用工厂函数(如list()、dict()、set())
  3. 使用copy模块中的copy()函数

常见深拷贝:

  1. 使用copy模块中的deepcopy()函数

在函数中的使用

在函数中:
可变对象为引用传递,不可变对象为值传递
值传递: 简单来说 对于函数输入的参数对象,函数执行中首先生成对象的一个副本,并在执行过程中对副本进行操作。执行结束后对象不发生改变
引用传递:当传递列表或者字典时,如果改变引用的值,就修改了原始的对象
我们可以通过对结果数据横向和纵向的比较

a= 3
b = [1,2]
c = [1,2,3]
d=(1,2)
print('定义阶段:',a,id(a),b,id(b),c,id(c),d,id(d))
def t1(a,b,c,d):
    print('函数头部 :',a,id(a),b,id(b),c,id(c),d,id(d))
    a = 2
    b =[]
    c.append(7)
    d=(1,2,3)
    print('函数尾部 :',a,id(a),b,id(b),c,id(c),d,id(d))
t1(a,b,c,d)
print('函数外  :',a,id(a),b,id(b),c,id(c),d,id(d))
#测试结果
定义阶段: 3 2042230736 [1, 2] 17420072 [1, 2, 3] 17602344 (1, 2) 17417096
函数头部 : 3 2042230736 [1, 2] 17420072 [1, 2, 3] 17602344 (1, 2) 17417096
函数尾部 : 2 2042230720 [] 17603272 [1, 2, 3, 7] 17602344 (1, 2, 3) 17459784
函数外  : 3 2042230736 [1, 2] 17420072 [1, 2, 3, 7] 17602344 (1, 2) 17417096

参考资料:
python小整数池
关于python变量的可变性
深入理解python对象系统
Python中变量在内存的存储与地址变化详解

猜你喜欢

转载自blog.csdn.net/m0_46141590/article/details/105909085