第五课 python进阶对象引用、可变性和垃圾回收
tags:
- Python
- 慕课网
categories:
- set
- dict
第一节 python中变量
- python和java中的变量本质不一样,python的变量实质上是一个指针 int str 好像便利贴
a = 1
a = "abc"
# 1. a贴在1上面
# 2. 先生成对象 然后贴便利贴
a = [1, 2, 3]
b = a
print(id(a), id(b))
print(a is b)
b.append(4)
print(a)
第二节 is和==
- is 判断对象是否相同
- == 判断对象值是否相同。
- python内部维护了一个常量池,在创建简单的整型和字符串型变量时。会先看下常量池中有没有,如果有就直接用。
- 常量池中变量它永远不会被GC机制回收, 只要定义的整数变量在 范围: -5 – 256内,会被全局解释器重复使用, 257除外
- 257 这个整数对象是区分作用域的,它只有在相同的作用域, 内存地址才会相同
a = [1, 2, 3, 4]
b = [1, 2, 3, 4]
print(id(a), id(b)) # 不等的
print(a is b)
print(a == b)
# 为了节省内存 这里有一个python内部有个常量池奥 注意
# a = 1
# b = 1
a = "abc"
b = "abc"
print(id(a), id(b)) # 这里是相等的
print(a is b)
print(a == b)
class People:
pass
# 类 是什么类用 is 不会用==
person = People()
if type(person) is People:
print("yes")
# 奇葩的257
def test_a():
a = 257
b = 257
print(id(a))
print(id(b))
print(id(a), id(b))
a = 257
b = 257
print(id(a), id(b))
第三节 垃圾回收和del语句
-
del并不是把对象回收了 而是计数减一了
-
如果我们想在对象回收前进行一些操作 那么可以重写__del__方法
-
cpython中垃圾回收的算法是采用 引用计数。但是循环引用是引用计数的致命伤。Python采用了“标记-清除”(Mark and Sweep)算法解决循环引用问题。
- 在标记清除算法中,为了追踪容器对象,需要每个容器对象维护两个额外的指针,用来将容器对象组成一个双端链表,指针分别指向前后两个容器对象,方便插入和删除操作。
- python解释器(Cpython)维护了两个这样的双端链表,一个链表存放着需要被扫描的容器对象,另一个链表存放着临时不可达对象。两个链表分别被命名为”Object to Scan”和”Unreachable”
- 下图:link1,link2,link3组成了一个引用环,同时link1还被一个变量A(其实这里称为名称A更好)引用。link4自引用,也构成了一个引用环。
-
gc启动的时候,会逐个遍历”Object to Scan”链表中的容器对象,并且将当前对象所引用的所有对象的gc_ref减一。这一步操作就相当于解除了循环引用对引用计数的影响。
-
接着,gc会再次扫描所有的容器对象,如果对象的gc_ref值为0,那么这个对象就被标记为GC_TENTATIVELY_UNREACHABLE,并且被移至”Unreachable”链表中。(第一次扫描)
-
同时当gc发现有一个节点是可达的,那么他会递归式的将从该节点出发可以到达的所有节点标记为GC_REACHABLE 。 link2和link3所碰到的情形 。(根据一个可达遍历其他可达)
-
上面描述的垃圾回收的阶段,会暂停整个应用程序,等待标记清除结束后才会恢复应用程序的运行。
-
在循环引用对象的回收中,整个应用程序会被暂停,为了减少应用程序暂停的时间,Python 通过**“分代回收”(Generational Collection)**以空间换时间的方法提高垃圾回收效率。 定义三种世代 三种世代(0,1,2) ,总的来说: 对象存在时间越长,越可能不是垃圾,应该越少去收集
-
gc的扫描在什么时候会被触发呢?答案是当某一世代中被分配的对象与被释放的对象之差达到某一阈值的时候,就会触发gc对某一世代的扫描。值得注意的是当某一世代的扫描被触发的时候,比该世代年轻的世代也会被扫描。也就是说如果世代2的gc扫描被触发了,那么世代0,世代1也将被扫描,如果世代1的gc扫描被触发,世代0也会被扫描。
# cpython中垃圾回收的算法是采用 引用计数
a = object()
b = a
# del并不是把a回收了 而是计数减一了在C++中删除就回收了
del a
print(b)
print(a)
# 如果我们想在对象回收前进行一些操作 那么可以重写__del__方法
class A:
def __del__(self):
pass
第四节 经典的参数错误
def add(a, b):
a += b
return a
class Company:
def __init__(self, name, staffs=[]):
self.name = name
self.staffs = staffs
def add(self, staff_name):
self.staffs.append(staff_name)
def remove(self, staff_name):
self.staffs.remove(staff_name)
if __name__ == "__main__":
com1 = Company("com1", ["bobby1", "bobby2"])
com1.add("bobby3")
com1.remove("bobby1")
print(com1.staffs)
# 这里需要注意呀 com2和com3对象没有传值 使用了默认的[]
# 就是这个默认的[] 同时引用给了com2和com3
# 上面传值的com1, 没有收到影响
com2 = Company("com2")
com2.add("bobby")
print(com2.staffs)
# 这个__defaults__可以看到默认值
print(Company.__init__.__defaults__)
#
com3 = Company("com3")
com3.add("bobby5")
print(com2.staffs)
print(com3.staffs)
print(com2.staffs is com3.staffs)
# a = 1
# b = 2
#
# 这里的a发生了变化
# a = [1,2]
# b = [3,4]
#
# a = (1, 2)
# b = (3, 4)
#
# c = add(a, b)
#
# print(c)
# print(a, b)