1. 内存分析
1.1 程序运行方式
Python执行一个程序:程序就从解释器申请内存
Python解释器:预加载->demo.py->demo01.pyc文件->内存->运行demo01.py程序代码
1.2 内存分配
1.2.1栈内存
栈内存(stack): 读取、加载。数据速度快但不是很稳定,适合存放经常临时分配、经常回收的数据变量。
1.2.2 堆内存
堆内存:读取、加载数据较慢;比较消耗资源,但是一旦数据存在,数据操作比较稳定适合存放的数据:对象
1.2.3 数据区
方法区|数据区[data]:专门加载程序运行的代码字节数据,方法数据、函数数据等等
1.2.4 静态存储区
静态存储区|常量区[static]:专门存放程序中的公共数据、静态数据的内存区域。
image.png1.3不可变数据类型|可便数据类型
1.3.1 不可变数据类型
一般数据类型都是不可变数据类型,不可变数据类型是在定义了数据之后,修改变量的数据,变量不会修改原来内存的地址的数据,而是会指向新的地址,原有的数据保留,这样方便程序中基本数据的利用率。
例如:整数类型:-5->256,在解释器加载时,已经自动分配了这些数字的内存,超出-5->256范围的整数,在一个代码块中申请一次内存。
交互模式:一行命令就是一个代码块
IDE模式-开发工具:一个模块就是一个代码块
b = 12
print(id(b))
b = 13
print(id(b))
输出:
1796173168
1796173200
1.3.3 可变数据类型
对象在内存地址中存储的数据可变
a = list() # 堆内存中:存在一个对象 list(),一个变量a指向这个对象
print(id(a)) # 查看对象a的内存
print(a)
a.append("hello")
print(id(a))
print(a)
输出:
1357340489992
[]
1357340489992
['hello']
可变类型|不可变类型 思考题:
nums = [12, 13, 14,"python",["hello","world"]]
a = 12
print(id(a),id(nums[0]))
b = "python"
print(id(b),id(nums[3]))
c = ["hello","world"]
print(id(c),id(nums[4]))
输出:
1796173168 1796173168
1324994797896 1324994797896
1324995705736 1324994820616
1.3 代码和代码块
Python中的最小运行单元是代码块,代码块的最小单元时一行代码
思考:a = ‘hello’在内存中会创建几块内存空间 =======>2
image.pngp = Person(‘tom’,18)在内存中,会创建几块内存空间 ===========>4
image.png1.4 程序内存代码检测
为了便于检测代码内存使用率,社区开发了一个模块memory_profile
通过 pip install memory_profiler
使用方法:通过测试的函数或者类型前面添加@profile注解,让内存分析模块可以直接进行代码进行检测
from memory_profiler import profile
class Person:
'''自定义类型'''
def __init__(self,name,age,gender):
self.name = name
self.age = age
self.gender = gender
@profile(precision=10)
def main():
'''入口函数'''
p = Person('tom',18,'男')
print(p)
p2 = Person('tom',18,'男')
print(p)
if __name__ == "__main__":
main
2 操作符号
2.1 is 和 == 和 isinstance 的使用
1. a is b :判断两个变量a/b,他们指向的对象是否时同一个对象
2. a == b :判断两个变量a/b,他们指向的对象的数据内容是否一致
3.isinstance(a,b)判断a是否属于b类型
3 引用|深拷贝|浅拷贝
3.1 引用
如果程序中多个不同的地方都要使用同一个对象,通过对象的引用赋值,将同一个对象赋值给多个变量。
应用赋值并不会产生新的对象,而是让多个变量可以共同指向一个对象,通过多个变量都可以操作同一个对象的数据。
class Person:
def __init__(self,name,fav):
self.name = name
self.fav = ["篮球","足球"]
a = Person("tom",["羽毛球"])
b = c = a
print(id(a))
print(id(b))
print(id(c))
输出:
2207567775728
2207567775728
2207567775728
3.2 浅拷贝
复制一个对象,复制对象中的属性数据的引用
方法:导入模块 copy
X = copy.copy(a) #拷贝了a对象,产生了一个对象x
class Person:
def __init__(self,name,fav):
self.name = name
self.fav = ["篮球","足球"]
# a = Person("tom",["羽毛球"])
# b = c = a
# print(id(a))
# print(id(b))
# print(id(c))
import copy
a = Person("tom","lol")
x = copy.copy(a)
x.fav.append('aa')
print(id(a)) #2519924135976
print(id(x)) #2519924136872
print(a.fav) #['篮球', '足球', 'aa']
print(x.fav) #['篮球', '足球', 'aa']
3.3 深拷贝
和对象的浅拷贝不同,对象的深拷贝,是对象数据的直接拷贝,而不是简单的引用拷贝,主要是通过python内建标准模块copy提供的deepcopy函数可以完成对象深拷贝。
4 垃圾回收机制
自动回收无效对象数据,通过垃圾回收算法进行操作
垃圾回收:Garbage Collection : GC
Python中,以引用计数垃圾回收算法为主要回收机制
以标记-清除 和 分代回收为辅助回收机制
4.1 引用计数
1.查询指定对象的引用数量
Import sys
s = sys.getrefcount(p) #查询p的引用数量为s
- python是一个面向对象的弱类型语言,所有的对象都是直接或者间接继承自object类型,object类型的核心其实就是一个结构体对象
typedef struct_object {
int ob_refcnt;
struct_typeobject *ob_type;
} PyObject;
在这个结构体中,ob_refcnt就是对象的引用计数,当对象被创建或者拷贝时该计数器就会增加1,当对象的引用变量被删除时就会减少1,当引用计数为0时,对象数据就会被释放。
4.2 标记清除
标记清除思想: 首先找到python中的一批根节点对象,通过根节点对象可以找到他们指向的子节点对象,如果搜索过程中有这个指向是从上往下的指向,表示这个对象是可达的,否则该对象是不可达的,可达部分的对象在程序中需要保留下来,不可达部分的对象在程序中时不需要保留的。
4.3 分代回收
在python内部处理机制中,定义了三个不同的链表数据结构[第0代,第一代,第二代]。Python为了提高程序执行效率,将垃圾回收机制进行了阈值限定,0代回收最为密集,其次是1代,最后是2代
4.4 垃圾回收处理
Python中的gc模块提供了垃圾回收处理的各项功能机制,必须import gc才能使用
gc.set_debug(flags):设置gc的debug日志,一般为gc.DEBUG_LAKE
gc.collect([generation]):显示进行垃圾回收处理,可以输入参数~参数表示回收的对象代数,0表示只检查第0 代对象,1表示检查第0、1代对象,2表示检查0,1,2代对象,如果不传递参数,执行FULL COLLECT,也就是默认传递2
gc.set_threshold(threshold0[,threshold2[,threshold3]]):设置执行垃圾回收机制的频率
gc.get_count():获取程序对象引用的计数器
gc.get_threshold():获取程序自动执行GC的引用计数阈值
在程序开发过程中需要注意:
l 项目代码中尽量避免循环引用
l 引入gc模块,启用gc模块自动清理循环引用对象的机制
l 将需要长期使用的对象集中管理,减少GC资源消耗
l gc模块处理不了重写del方法导致的循环引用,如果一定要添加该方法,需要显式调用gc模块的garbage中对象的del方法进行处理
`
网友评论