深浅拷贝
对不可变类型的数据
深浅拷贝都是引用同一个对象地址
a=23
b=a
import copy
c=copy.copy(a)
d=copy.copy(b)
e=copy.deepcopy(b)
print(id(a))
print(id(b))
print(id(c))
print(id(d))
print(id(e))
"""
140559205407728
140559205407728
140559205407728
140559205407728
140559205407728
"""
对可变类型的数据
深浅拷贝会各自产生一个内存地址
a=[1,2,3]
b=a
import copy
c=copy.copy(a)
d=copy.copy(b)
e=copy.deepcopy(b)
print(id(a))
print(id(b))
print(id(c))
print(id(d))
print(id(e))
"""
140385261197248
140385261197248
140385261207680
140385261196992
140385212854208
"""
深浅拷贝之间的区别:
普通的赋值
比如a=b,就是把对象的引用地址复制一遍而已,还是指向同一个内存地址。
浅拷贝
a=[1,['a','b']]
b=copy.copy(a)
不拷贝子对象。比如对a新增一个元素,不影响b,但是如果对a中的列表元素新增一个,就会影响b
深拷贝
c=copy.deepcopy(a) 则是把所有元素都拷贝了一分,完全独立,互不影响。
import copy
a=[1,['a','b']]
b=copy.copy(a)
print(f'{a=}')
print(f'{b=}')
a.append('新元素1')
a[1].append('新元素2')
print(f'{a=}')
print(f'{b=}')
"""
a=[1, ['a', 'b']]
b=[1, ['a', 'b']]
a=[1, ['a', 'b', '新元素2'], '新元素1']
b=[1, ['a', 'b', '新元素2']]
"""
python内存管理机制 & 垃圾回收机制
备注
- 多个元素组成的对象,比如列表,元组,字符串等
- float类型数据,它的结构体里存储了4+1个元素【(两个双向链表指针、引用计数器、类型)+(该类型独有元素)】
- list/tuple/int/str/dict/set类型数据,它的结构体里存储了5+1个元素【(两个双向链表指针、引用计数器、类型、元素个数)+(该类型独有元素)】
- 当引用计数为0的时候,按道理应该要进行垃圾回收,销毁它,释放内存。但是python内部有一个驻留机制,类似**“内存空间缓存”**的东西。
- 比如float对象就有个最多可存100个空间的free_list,list对象则有个最多可存80的free_list,
- python会把这块本应释放的空间放进去,如果下次还创建同类型的对象,就从这里取出来用。
- 注意:
- int,str对象不是用free_list,但也有类似的驻留机制。
- 有些对象,没有这种缓存机制。
内存管理机制
垃圾回收机制
引用计数为主,标记清除和分代回收为辅
-
引用计数:同上
- 问题:引用计数会出现循环引用(容器类型的对象才会发生)
- 例子:
a=[1,2] b=[3,4] a.appen(b) #b的引用计数=2 b=append(a) #a的引用计数=2 del a del b;此时a/b引用计数=1,未被回收,始终存在于内存中。(内存泄漏) -
标记清除:
- 针对那些容器类的对象,Python创建的时候会把它们单独加到一个新的双向链表里,定期扫描,检查是否有循环引用,如果有则各自-1,如果-1之后等于0,就进入回收阶段(若有驻留机制也会执行)
- 非容器类对象,python创建的时候会把它们单独加到一个双向链表里,与容器类对象的双向链表区别开。
-
分代回收:
- 针对标记清除,为了少扫描对象,将没有问题的对象放到上一级链表中(0代链表,1代链表,2代链表),默认下一级链表扫10次,上一级才扫描1次。
图文解析: