python中可变对象/不可变对象、栈/堆管理、深浅拷贝

本文详细解释了Python中的可变对象(如列表、字典)和不可变对象(如数字、字符串)在内存中的存储机制,区分了栈和堆的区别。同时介绍了栈帧的概念,以及浅拷贝与深拷贝在对象复制中的行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

python中可变对象/不可变对象、栈/堆管理

内存中的堆栈和数据结构堆栈不是一个概念,可以说内存中的堆栈是真实存在的物理区,数据结构中的堆栈是抽象的数据存储结构。

内存空间在逻辑上分为三部分:代码区、静态数据区和动态数据区,动态数据区又分为栈区和堆区。

代码区: 存储方法体的二进制代码。高级调度(作业调度)、中级调度(内存调度)、低级调度(进程调度)控制代码区执行代码的切换。

静态数据区: 存储全局变量、静态变量、常量,常量包括final修饰的常量和String常量。系统自动分配和回收。

栈区: 存储运行方法的形参、局部变量、返回值。由系统自动分配和回收。

堆区: new一个对象的引用或地址存储在栈区,指向该对象存储在堆区中的真实数据。

在 Python 中,数字、字符串等基本类型的对象都是不可变的,其大小是固定的,因此可以直接存储在栈空间中

栈是一种具有后进先出(Last In First Out)特性的数据结构,栈空间通常用于存储函数调用时的局部变量和函数参数等信息。在函数调用过程中,会按照调用顺序依次进入栈中,而在函数返回时,则会按照相反的顺序依次从栈中弹出。因此,栈空间被广泛应用于函数调用和局部变量存储的原因。当函数被调用时,操作系统会为函数分配一块栈空间,用于存储函数调用时需要使用的局部变量和参数等信息。这些信息通常是在函数调用时创建的,当函数返回时,栈空间也会被自动销毁,这样可以确保内存的高效使用和安全性。

在 Python 中,数字、字符串等基本类型的对象都是不可变的,它们的值在创建后就不会发生变化。因此,Python 可以将它们直接存储在栈空间中,不需要像列表、字典等可变类型一样,动态调整内存大小,从而提高了程序的执行效率。而对于可变类型的对象,Python 通常会将它们存储在堆空间中,这样可以动态调整内存大小,确保程序的灵活性和可靠性。

在 Python 中,列表和字典等复杂类型的对象是动态分配的,其大小不固定。为了方便存储和管理,这些对象通常存储在堆空间中

当定义一个列表或字典时,Python 会先在栈空间中分配一块空间,用于存储该对象的引用。该引用指向一个在堆空间中分配的内存块,该内存块中存储了实际的列表或字典对象。因为列表和字典等对象的大小不固定,可能需要动态地调整内存大小,所以通常使用动态内存分配方式,即在堆空间中分配一块较大的内存池,然后根据实际需要动态地申请和释放内存块。

需要注意的是,在 Python 中,对象的引用和实际的对象是分开存储的,因此,当多个变量引用同一个对象时(变量就是对象的引用),它们都指向同一个内存块。这也就意味着,当其中一个变量改变了对象的值时,其他引用该对象的变量也会受到影响。

可变对象与不可变对象的操作

a = 1
b = a
print(id(a), id(b)) #2780123588912, a, b两者内存地址相同
a = 1
b = a
print(id(a), id(b)) #2780123588912,两者内存地址相同
a = a + 1 # 等效于a += 1
print(id(a + 1)) # 2065685571952
print(id(a)) # 2065685571920

a = a + 1 和 a += 1,对于数字、字符串等基本类型的不可变对象来说,这种操作相当于另外在堆开辟一个内存空间,生成新对象2,然后将内存地址传给对象a。【个人认为是把a拷贝出来然后再进行操作】

a = [1]
b = a
print(id(a), id(b))
a += [1]
print(id(a))

a = a + [1] 和 a += [1],对于列表和字典等复杂类型的这种可变对象来说,这种操作相当于就在对象a的原内存地址进行操作

函数内堆与栈

a = 3
def test01():
    b = 4
test01()
test01()

在这里插入图片描述
如上图,每一个对象都是个内存块,栈里面建立对象a、然后在堆里生成对象3,并且将对象3的内存地址给对象a,栈里面对象a保存的是3的内存地址;
函数test01也是作为一个对象;
当调用函数test01的时候,会新建一个函数栈帧,栈帧存放我的局部对象,局部对象的值放在堆里,调用完后函数栈帧会释放掉。

栈帧
每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量,从百度中我们可以获取信息如下:
栈帧是一块因函数运行而临时开辟的空间。
每调用一次函数便会创建一个独立栈帧。
栈帧中存放的是函数中的必要信息,如局部变量、函数传参、返回值等。
当函数运行完毕栈帧将会销毁。

def foo1():
    a = 1
    b = 1

在这里插入图片描述
LOAD_CONST是加载局部作用域(函数)的对象,STORE_FAST是局部作用域(函数)保存值到对象
创建对象foo1,接着调用对象foo1,生成foo1函数栈帧;
碰到foo1函数内第一个语句时【a = 1】,会在栈帧内生成对象a,然后=右边的1会被LOAD_CONST加载为对象放入堆中, 接着STORE_FAST1地址传给栈帧中a对象中;
foo1函数内第二个语句时【b = 1】同理。

def foo2():
    a, b = 1, 2

在这里插入图片描述

UNPACK_SEQUENCE(count)TOS 解包为 count 个单独的值
如上图,foo2函数会生成ab对象,压入栈中,然后将生成((1, 2))对象,放入堆中;
通过UNPACK_SEQUENCE(count) 序列解包,接着把解包结果按顺序写入ab对象中

import dis  # 导入dis库
def bar():
    a, b = b, a

在这里插入图片描述

如上图,bar函数会生成ab对象,压入栈中;
交换两个最顶层的堆栈项。

浅拷贝与深拷贝

浅拷贝

import copy
a = [1, 2, [3, [1]]]
b = a.copy()
print(id(a) # 1865060003456
print(id(b)) # 1865060003520
print(a, b) # [1, 2, [3, [1]]] [1, 2, [3, [1]]]
a[0] = 100
a[2][1][0] = 100 
print(id(a), id(b)) # 1865060003456 1865060003520
print(a, b) # [100, 2, [3, [100]]] [1, 2, [3, [100]]]

浅拷贝出来的对象,内存地址和拷贝对象是不一样的,第一层内存地址值是独立的,所以a[0] = 100后,ab第一层数会变;

a = [100, 2, [3, [100]]]
a = [1, 2, [3, [100]]]

但是第二层继续引用拷贝对象的内存地址,a[2][1][0] = 100 会影响ab。(拷贝的不独立不完全,所以认为是浅拷贝)

深拷贝

a = [1, 2, [3, [1]]]
b = copy.deepcopy(a)
print(id(a), id(b)) # 2617664876160 2617664876288,内存地址不同
print(a, b) # [1, 2, [3, [1]]] [1, 2, [3, [1]]]
a[2][1][0] = 100
a[0] = 100
print(id(a), id(b)) # 2352369220608 2352366445888
print(a, b) # [100, 2, [3, [100]]] [1, 2, [3, [1]]]

这里可以看到深拷贝出来的,完全不受影响,是完全独立的

参考文章

https://juejin.cn/post/7142675089694130213

https://blog.csdn.net/weixin_41777118/article/details/130396984

https://blog.csdn.net/qq_34159047/article/details/109229108#:~:text=%E7%AE%80%E8%A8%80%E4%B9%8B%EF%BC%9A%20%E5%AF%B9%E4%BA%8E%E5%8F%AF%E5%8F%98%E5%AF%B9%E8%B1%A1%EF%BC%8C%20a%20%2B%3D%201%20%E7%9B%B4%E6%8E%A5%E5%9C%A8%E5%8E%9F%E5%86%85%E5%AD%98%E5%9C%B0%E5%9D%80%E4%B8%8A%E6%93%8D%E4%BD%9C%20a%20%3D,1%20%E5%92%8C%20a%20%3D%20a%20%2B%201%20%E9%83%BD%E6%98%AF%E5%9C%A8%E6%96%B0%E5%BC%80%E8%BE%9F%E7%9A%84%E7%A9%BA%E9%97%B4%E6%93%8D%E4%BD%9

https://docs.python.org/zh-cn/3.8/library/dis.html#opcode-collectionsC

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值