Python基础

163 阅读3分钟

深浅拷贝

对不可变类型的数据

深浅拷贝都是引用同一个对象地址

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内存管理机制 & 垃圾回收机制

备注

  1. 多个元素组成的对象,比如列表,元组,字符串等
  • float类型数据,它的结构体里存储了4+1个元素【(两个双向链表指针、引用计数器、类型)+(该类型独有元素)】
  • list/tuple/int/str/dict/set类型数据,它的结构体里存储了5+1个元素【(两个双向链表指针、引用计数器、类型、元素个数)+(该类型独有元素)】
  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次。

图文解析: