python垃圾回收机制
python采用以引用计数为主,标记清除和隔代回收为辅的垃圾回收机制。(具体的内容参考------> python垃圾回收机制;博主解释的非常清晰易懂,在这里只是对该博客做一个学习笔记,方便本人日后复习。)
垃圾回收机制要解决的问题
内存泄漏
由于一个长期持有的对象不断地往一个dict(字典)或者list对象中添加新的对象,而又没有及时释放,就会导致这些对象占用的内存越来越多。
常见的分析工具:
- gc
- objgraph
- memory.profiler
- tracemalloc
- filprofiler
- pympter
悬空指针
定义:
主要是在C语言中,指针可以指向内存,如果这块内存稍后被操作系统回收,但是当前指针仍然指向这块内存,此时该指针就是悬空指针。
解决办法:一旦指针被释放就将指针赋值为NULL
野指针(补充):
定义:不确定指针的具体指向,主要指向未初始化的指针。野指针的危害比悬空指针的危害还要大。
例如:Void *p;
解决办法:赋初值 Void *p = NULL
引用计数
定义
- 在python中维护了一个refchain的双向环状链表(这个链表中存储创建的所有对象),在运行程序时,每创建一个对象会根据数据类型的不同,找到对应的结构体,根据结构体中的字段,将新创建的对象加入这个环形链表中;
- 而每种类型的对象中,都有一个ob_refcnt(默认是1)引用计数器的值,它维护者引用的个数+1,-1,最后当引用计数器变为0时,则进行垃圾回收(对象销毁、refchain中移除)。
环形链表中所存放数据的相同点和不同点
- 相同点,都包含四部分内容(指向上一个对象的指针,指向下一个对象的指针、类型、引用的个数)
# 内部会创建一些数据,【指向上一个对象的指针、指向下一个对象的指针、类型、引用的个数】
age = 18 # 整形对象
# 内部会创建一些数据,【指向上一个对象的指针、指向下一个对象的指针、类型、引用的个数】
hobby = ["吸烟","喝酒","烫头"] # 列表对象
- 不同点:不同的数据类型会创建不同的值:
# 内部会创建一些数据,【指向上一个对象的指针、指向下一个对象的指针、类型、引用的个数、val=18】
age = 18 # 整型对象
# 内部会创建一些数据,【指向上一个对象的指针、指向下一个对象的指针、类型、引用的个数、items=元素、元素的个数】
hobby = ["抽烟","喝酒","烫头"] # 列表对象
data = 3.14
内部会创建:
_ob_next = refchain中的上一个对象
_ob_prev = refchain中的后一个对象
ob_refcnt = 1 引用个数
ob_type= float 数据类型
ob_fval = 3.14
无法解决循环引用问题
v1 = [1,2,3] # refchain中创建一个列表对象,由于v1=对象,所以列表引对象用计数器为1.
v2 = [4,5,6] # refchain中再创建一个列表对象,因v2=对象,所以列表对象引用计数器为1.
v1.append(v2) # 把v2追加到v1中,则v2对应的[4,5,6]对象的引用计数器加1,最终为2.
v2.append(v1) # 把v1追加到v1中,则v1对应的[1,2,3]对象的引用计数器加1,最终为2.
del v1 # 引用计数器-1
del v2 # 引用计数器-1
最终v1,v2引用计数器都是1
标记清除
引用计数机制无法解决循环引用问题,因此python引入标记-清除算法解决循环引用问题。
原理:
在python的底层,再去维护一个链表,在这个链表中专门去存放存在循环引用的对象(list/ dict/ tuple/ class),然后在某种情况下,扫描可能存在循环引用的链表中的每个元素,如果检查到循环引用,就让双方的引用计数减1,如果是0,则进行垃圾回收。
存在查找活动对象效率低下的问题
【标记清除(Mark—Sweep)】算法是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法。它分为两个阶段:第一阶段是标记阶段,GC会把所有的『活动对象』打上标记,第二阶段是把那些没有标记的对象『非活动对象』进行回收。因此,在清除非活动对象之前必须顺序扫描整个堆内存,标记和清除的效率不高。
隔代回收
由于在标记-清除算法中,在清除非活动对象之前必须顺序扫描整个堆内存,标记和清除的效率不高,因此python又引入了隔代回收(或分代回收)
原理
根据弱代假说(年轻的对象通常死得也快,而老对象则很有可能存活更长的时间)将第二个链表(可能存在循环引用的链表),维护成3个环状双向的链表:
0代: 0代中对象个数达到700个,扫描一次。
1代: 0代扫描10次,则1代扫描1次。
2代: 1代扫描10次,则2代扫描1次。
频繁的处理零代链表中的新对象,可以将让Python的垃圾收集器把时间花在更有意义的地方:它处理那些很快就可能变成垃圾的新对象。同时只在很少的时候,当满足一定的条件,收集器才回去处理那些老变量。
猴子补丁
定义
monkey patch允许在运行期间动态修改一个类或模块(注意python中一切皆对象,包括类、方法、甚至是模块)功能就是动态的属性的替换。
猴子补丁的使用
class A:
def func(self):
print("Hi")
def monkey(self):
print("Hi, monkey")
a = A()
A.func=A.monkey #在运行的时候,才改变了func
a.func()
'''运行结果
Hi, monkey
'''
缺点
因为猴子补丁破坏了封装,且容易导致程序与补丁代码的实现细节紧密耦合,所以只是临时的变通方案。
python反射机制
定义
把字符串映射到实例(对象)的变量或者实例(对象)的方法然后可以去执行调用、修改等操作。
核心:通过字符串去操作对象的属性和方法
主要包含四个方法
方法 | 意义 |
---|---|
getattr(object,name[,default]) | 通过name返回object的属性值,当属性不存在,返回default默认值,如果没有default,则抛出异常,name必须是字符串 |
setattr(object,name,value) | 操作object的属性,如有则覆盖,不存在则新增 |
hasaattr(object,name) | 判断对象是否有这个名字的属性,name必须为字符串,有返回True,不存在返回Felse |
delattr(object,name) | 当通过实例来删除属性时调用此方法 |
反射方法的查找路径:
对象本身__dict__---->class的__dict__---->继承的祖先类(直到object)的__dict__
Note: attributes 和 functions 都存在__dict__ 里面
应用场景
在项目开发中尤为重要,在不清楚方法或变量在对象中是否存在时,可以通过反射这个特殊的方法或机制来对对象中"未知的"变量或者方法进行操作。
- 实例化对象进行反射
- 类的反射
- 模块间的反射
- 通过字符串导入模块(从文件中读出的字符串,想转换成变量的名字)
- 模拟FTP动态请求过程(将网络传输的字符串转换成变量的名字)
参考:python基础:反射(详解)
python的反射
python反射详解
python 自省机制
定义
在编程语言中,自省是指自我检查行为。检查某些事物以确定它是什么,它能做什么。
具体的来说,就是OPP(面向对象)语言在程序运行时,能够知道对象的类型,部分语言还能够知道对象所拥有的属性。
常见的自省机制
在python中,常见的自省(introspection)机制有许多,如:==dir()、type()、hasattr()、isinstance()==等,通过这些函数,我们可以在运行时得知对象的类型和属性。
dir()
它是用于自省的最重要的函数之一。它以列表的形式返回一个对象所拥有的全部属性和方法,如果dir()不传任何参数,默认是查找当前命名空间有什么对象。
a = 2333
print(dir(a))
# 输出省略...
当我们记不太清某个对象的某个方法的名字时,使用这个是非常有帮助的。
type( )
type()函数返回一个对象的类型。例如:
print(type('tigeriaf'))
# 结果输出为 <class 'str'>
print(type(2))
# 结果输出为 <class 'int'>
print(type([1, 2, 3]))
# 结果输出为 <class 'list'>
id( )
id()函数返回对象的唯一标识符,是一个整数,在CPython中id()函数用于获取对象的内存地址。例如:
print(id('tigeriaf'))
# 结果输出为 51064768
hasattr( )
虽然使用dir()函数会返回一个对象属性的列表,但我们有时候的需求只是判断一个对象是否含有某一个或者多个属性,这样,我们就可以使用hasattr()函数来完成,它返回一个bool值:
import json
print(hasattr(json, "dumps"))
getattr( )
使用hasattr()函数判断对象是否有某个属性值后,我们可以配合getattr()函数来获取其属性值:
print(getattr(json, "__path__"))
isinstance( )
使用isinstance()函数可以确定一个对象是否是某个特定类型或者定制类的实例:
class A:
pass
class B(A):
pass
def foo():
pass
obj = B()
print(isinstance(B,MethodType)) # 返回true,判断是否是类方法
print(isinstance(obj, A)) # true
print(isinstance(foo, FunctionType)) # 返回true,判断是否是函数
callable( )
使用callable()函数可以确定一个对象是否是可调用对象,比如函数对象,类对象等等,他们都是可调用的对象:
callable("23333") # false
callable(str) # true
inspect模块
inspect是Python的标准库,提供了更加强大的自省能力,提供了很多函数帮助获取对象的信息,例如模块、类、方法、函数、回溯、帧对象以及代码对象。
该模块提供了4种主要的功能:类型检查、获取源代码、检查类与函数、检查解释器的调用堆栈。
详解参考: Python强大的自省机制
python的自省机制