Python运行浅析

运行环境#

代码执行#

Python解释器并不将Python编译成机器码运行,而是由Python虚拟机逐条解释,这也是Python为什么被称之为解释行语言,但是Python虚拟机并不会直接执行.py文件,其是由Python虚拟机执行解释之后的字节码

虚拟机运行过程简介#

  • 首先创建Python解释器的主线程状态对象 其是整个进程的根对象
  • 初始化内置类型 数字,列表有专门的缓存策略处理
  • 初始化__builtins__模块 该模块包含所有的数据类型与方法
  • 创建sys模块 该模块包含了sys.path与所有modules等重要信息
  • 初始化 import机制
  • 初始化内置 Exception。
  • 创建 __main__准备所需要的名称空间
  • 通过 site.py执行 site-packages中第三方模块添加到搜索路径中
  • 执行入口 py文件 执行前将 __main__ __dict__做为名字空间传递
  • 程序执行结束
  • 执行清理操作 包括调用退出函数 GC清理模块 释放所有模块等
  • 终止进程

Python文件运行过程#

  1. python会将py文件边写成字节码文件
  2. 将编译之后的字节码文件传递给虚拟机
  3. 虚拟机会从编译之后的PyCodeObject得到字节码指令且执行该指令等

.Pyc文件#

  1. .pyc文件是字节码在磁盘上的表现
  2. 执行 python test.py会将该文件编译成字节码但是并不会生成 .pyc文件
  3. 如果 test.py文件导入模块例如 re模块此时python会对re进行编译成字节码文件 生成 re.pyc文件然后对字节码进行解释执行
  4. 如果想生成 test.pyc文件可以使用内置的模块py_compile也可以使用 python -m test.py
  5. 加载模块时候如果同时存在 .pyc.py文件则会使用 .pyc运行 如果 .pyc的编译时间早于 .py的编译时间则会执行 .py文件并且更新.pyc文件
Copy
python -m test.py

image-20210312212851693

类型和对象#

先有类型(Type),而后才能生成实例(instance),Python中一切都是对象,包括实例内的对象都包含一个标准头,通过头部信息既可以知道明确的数据类型

头部信息由引用计数类型指针组成,前者在对象被引用的时候计数增加,当超出作用域或者被释放的时候计数减小,等于0的时候会被虚拟机回收(某些被缓存的对象计数器永远不会为0)

引用计数#

Copy
# !/usr/bin/env python3 # -*- coding: utf-8 -*- # @Time : 2021/3/11 下午7:13 # @Author : SR # @Email : srcoder@163.com # @File : test.py # @Software: PyCharm import sys b = 10086 print(sys.getrefcount(b)) c = b # 对与b进行引用 引用计数增加 print(sys.getrefcount(b)) del c # 删除对上述b的引用 引用技术减小 print(sys.getrefcount(b))

image-20210312224027043

类型指针#

类型指针指向具体的类型对象,其中包含了继承关系,静态成员信息等,所有的内置数据类型都可以从types模块中找到

Copy
# !/usr/bin/env python3 # -*- coding: utf-8 -*- # @Time : 2021/3/11 下午7:13 # @Author : SR # @Email : srcoder@163.com # @File : test.py # @Software: PyCharm import types x = 10 print(x.__class__) # 查看内置的类型 print(type(x) is types.IntType) # True print(x.__class__ is type(x) is types.IntType is int) # True y = x print(id(x), id(y)) print(id(int), id(types.IntType)) # 同一类型 内存地址一样

image-20210313103629242

名字空间#

Copy
x = 10 # 我们习惯将x称为变量 但是准确来说其应该称为名字

Python的名字实际上是一个字符串对象,其和所对应的值一起在名字空间中构造成一项 {"name":"object"}关联

Python有多种名字空间,被称之为 globals的模块名字空间,被称之为 locals的函数堆栈名字空间,不同的名字空间决定了对象的作用域以及生存周期

Copy
# !/usr/bin/env python3 # -*- coding: utf-8 -*- # @Time : 2021/3/11 下午7:13 # @Author : SR # @Email : srcoder@163.com # @File : test.py # @Software: PyCharm x = 10 print(type(globals())) # 字典 print(globals().get('x')) # 字典取值

image-20210313105505310
从上述结果展示可以看出名字空间即为一个字典对象,可以通过字典创建 key:value键值对的方法对名字空间中添加名字

Copy
# !/usr/bin/env python3 # -*- coding: utf-8 -*- # @Time : 2021/3/11 下午7:13 # @Author : SR # @Email : srcoder@163.com # @File : test.py # @Software: PyCharm globals()['name'] = 'SR' # 添加键值对 print(globals()) # 查看名字空间 print(globals().get('name')) # 获取名字空间所对应的值

image-20210313110124463
名字的作用仅仅是在某个时刻将名字空间内的某个对象进行关联,其本身并不包含任何对象信息,只有通过对象的头部信息才能获知晓相关的数据类型,进而进行相应的数据查找,因为名字的弱类型特征,我们可以在程序的运行过程之中,随时更改其数据类型         

Copy
# !/usr/bin/env python3 # -*- coding: utf-8 -*- # @Time : 2021/3/11 下午7:13 # @Author : SR # @Email : srcoder@163.com # @File : test.py # @Software: PyCharm print(type(globals()['name'])) globals()['name'] = int # 将原本与字符串关联的名字指向整形 print(globals()['name'])

image-20210313120857738
在函数外部 locals()globals()作用完全相同,但是在函数内部 locals()则是获取当前函数的名字空间,其中存储的是函数参数,局部变量信息等

Copy
# !/usr/bin/env python3 # -*- coding: utf-8 -*- # @Time : 2021/3/11 下午7:13 # @Author : SR # @Email : srcoder@163.com # @File : test.py # @Software: PyCharm print(locals() is globals()) # True print(locals()) print(id(locals())) # 140543843598912 print(id(globals())) # 140543843598912

image-20210313151412262

Copy
# !/usr/bin/env python3 # -*- coding: utf-8 -*- # @Time : 2021/3/11 下午7:13 # @Author : SR # @Email : srcoder@163.com # @File : test.py # @Software: PyCharm def test(x): y = x + 100 print(locals()) # {"y": 223, "x": 123} print(locals() is globals()) # True frame = sys._getframe(0) # 获取当前堆栈帧 print(locals()) {'y': 223, 'x': 123, 'frame': <frame object at 0x24d75e8>} print(globals()) # {'test': <function test at 0x7f72f0b6aea0>} 函数外部可以定义函数的名字空间 test(123)

内存管理#

为提升工作性能,Python在操作系统中开辟了内存池以便减少对内存的分配与回收操作,对于字节小于 256 字节对象,将直接从内存池中获取存储空间

根据需求,虚拟机每次从操作系统中申请一块256KB取名为 arena 的内存,并且将该内存分割成多个 pool,在每个pool中在进行划分大小相同的 block

block大小是8的倍数,因此假如需要开辟13字节大小的空间,需要 block大小为16内存池来获取空闲块,所有块都用链表与头信息进行管理

以便快速查找空闲区域进行分配

⼤于 256 字节的对象,直接⽤ malloc 在堆上分配内存。程序运⾏中的绝⼤多数对象都⼩于这个阈值,因此内存池策略可有效提升性能。

当所有的arena的总容量超出限制64MB的时候,其不会再次请求arena内存,而是直接在堆上为对象分配内存,另外空闲的arena也会被释放交还操作系统

引用传递#

对象总是按照引用传递,即通过复制指针使多个变量指向一个内存地址,因为arena也是作用在堆上.因此无论何种类型何种大小都是直接存储在堆上,Python没有值类型与引用类型

Copy
# !/usr/bin/env python3 # -*- coding: utf-8 -*- # @Time : 2021/3/11 下午7:13 # @Author : SR # @Email : srcoder@163.com # @File : test.py # @Software: PyCharm a = object() b = a print(a is b) # True print(id(a)) print(id(b)) def test(x): print(id(x)) print("\n") test(a)

image-20210313165847800

深浅拷贝#

浅拷贝

浅拷贝是对一个对象父级(外层)拷贝,不会拷贝(内部)子级

父级对象为可变类型
  • 当外层为可变对象的时候复制生成的对象会开辟新的地址空间
  • 外层为可变对象原值修改不会影响拷贝之后的值
Copy
# !/usr/bin/env python3 # -*- coding: utf-8 -*- # @Time : 2021/3/11 下午7:13 # @Author : SR # @Email : srcoder@163.com # @File : test.py # @Software: PyCharm import copy l = [i * i for i in range(10)] # 生成一个新的列表 l2 = copy.copy(l) print(l is l2) # False print(id(l)) # 140221885816416 print(id(l2)) # 140221885958048

image-20210313175540808

Copy
# !/usr/bin/env python3 # -*- coding: utf-8 -*- # @Time : 2021/3/11 下午7:13 # @Author : SR # @Email : srcoder@163.com # @File : test.py # @Software: PyCharm import copy l = [i * i for i in range(10)] l2 = copy.copy(l) l.append(99) # 修改原值 print(l) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 99] print(l2) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

image-20210313180645796

父级对象为不可变类型
  • 当外层对象为不可变类型的时候复制之后的对象会引用原对象地址
  • 外层对象为不可变类型的时候原值改变复制之后的值也会改变
Copy
# !/usr/bin/env python3 # -*- coding: utf-8 -*- # @Time : 2021/3/11 下午7:13 # @Author : SR # @Email : srcoder@163.com # @File : test.py # @Software: PyCharm import copy a = 'hello' b = copy.copy(a) print(a is b) # True print(id(a)) # 140178072833984 print(id(b)) # 140178072833984

image-20210313181800742

Copy
# !/usr/bin/env python3 # -*- coding: utf-8 -*- # @Time : 2021/3/11 下午7:13 # @Author : SR # @Email : srcoder@163.com # @File : test.py # @Software: PyCharm import copy a = ([1, 2, 3],) b = copy.copy(a) print(b) # 拷贝之后的值 ([1, 2, 3],) a[0].append('4') print(a) # 修改之后的原值 ([1, 2, 3, 123123123],) print(b) # 修改之后的拷贝的值 ([1, 2, 3, 123123123],) print(id(a)) # 33803008 print(id(b)) # 33803008

image-20210313182240874

深拷贝

深拷贝是对对象内部外部都进行拷贝(递归)

父级对象为可变对象
  • 外层对象为可变对象的时候无论内部为什么数据类型都会开辟新的内存空间
  • 外层对象为可变对象的时候原值改变不会影响复制之后的值
Copy
# !/usr/bin/env python3 # -*- coding: utf-8 -*- # @Time : 2021/3/11 下午7:13 # @Author : SR # @Email : srcoder@163.com # @File : test.py # @Software: PyCharm import copy l = [i * 2 for i in range(10)] l1 = copy.deepcopy(l) print(l1) print(id(l)) # 140562607271520 print(id(l1)) # 140562607270368

image-20210313183909980

Copy
# !/usr/bin/env python3 # -*- coding: utf-8 -*- # @Time : 2021/3/11 下午7:13 # @Author : SR # @Email : srcoder@163.com # @File : test.py # @Software: PyCharm import copy l = [i * 2 for i in range(10)] l1 = copy.deepcopy(l) l.append(20) # 修改原值 print(l1) print(id(l)) print(id(l1))

image-20210313184448030

父级对象为不可变对象
  • 外层对象为不可变对象内层为可变对象会开辟新的内存地址
  • 外层对象为不可边对象内层为可变对象的时候原值修改不会影响复制之后的值
Copy
# !/usr/bin/env python3 # -*- coding: utf-8 -*- # @Time : 2021/3/11 下午7:13 # @Author : SR # @Email : srcoder@163.com # @File : test.py # @Software: PyCharm import copy l = ([i for i in range(10)],) l1 = copy.deepcopy(l) print(id(l)) # 140310375300368 print(id(l1)) # 140310375107536

image-20210313185734008

Copy
# !/usr/bin/env python3 # -*- coding: utf-8 -*- # @Time : 2021/3/11 下午7:13 # @Author : SR # @Email : srcoder@163.com # @File : test.py # @Software: PyCharm import copy l = ([i for i in range(10)],) l1 = copy.deepcopy(l) l[0].append(10) print(l1) # ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9],)

image-20210313185953451

垃圾回收#

引用计数#

Python默认使用引用计数,对象被引用计数会增加一次,当对象计数为0的时候,该对象内存资源会被回收,要么所对应的block块被置为空闲要么将资源返回给操作系统

Copy
# !/usr/bin/env python3 # -*- coding: utf-8 -*- # @Time : 2021/3/11 下午7:13 # @Author : SR # @Email : srcoder@163.com # @File : test.py # @Software: PyCharm import sys class User(object): def __del__(self): print("计数为0 释放资源") user = User() print(sys.getrefcount(user)) # 2 a = user print(sys.getrefcount(user)) # 3 del a # 删除最后一个引用

image-20210313192012203
某些内置类型,例如小整数由于缓存原因计数永远不会为0,直到进程结束由虚拟机调用释放函数进行清理

除了直接引用,Python还支持弱引用,允许在不增加计数,不影响释放的情况下间接引用对象,但不是所有数据类型都支持若引用,例如列表,字典等

Copy
# !/usr/bin/env python3 # -*- coding: utf-8 -*- # @Time : 2021/3/11 下午7:13 # @Author : SR # @Email : srcoder@163.com # @File : test.py # @Software: PyCharm import sys import weakref class User(object): def test(self): print("测试弱引用") def callback(x): # 回调函数会在原对象被回收的时候被引用 print("weakref object:%s" % x) print("target object dead!") user = User() we = weakref.ref(user, callback) # 创建弱对象引用 print(sys.getrefcount(user)) # 2 计数为2是getrefcount形参增加的 上述若引用并没有增加计数 print(we() is user) # 通过弱引用可以访问原对象 we().test() # 弱引用访问原对象类中的方法 del user # 删除原对象 会调用回调函数callback print(hex(id(we))) # 回调函数中的参数为弱对象 因为原对象已经被删除 print(we() is None) # 此时原对象被删除 因此弱对象只能返回None

image-20210313230308314

分代清除#

Python除引用计数之外,还有专门处理循环的GC

引发循环引用问题的一般都是容器类对象,例如 list set object等,对于该类容器对象,一般都是由 GC管理,但是并不是说此类对象就是有 GC进行管理,当没有发生循环引用的时候还是由积极性更高的计数器进行回收

Copy
# !/usr/bin/env python3 # -*- coding: utf-8 -*- # @Time : 2021/3/11 下午7:13 # @Author : SR # @Email : srcoder@163.com # @File : test.py # @Software: PyCharm import gc class User(object): def __del__(self): print(hex(id(self)), 'will be dead') disable_gc = gc.disable() # 关闭gc user = User() # 实例化一个对象 print(hex(id(user))) # 0x7fa46c869150 del user # 回收对象,引用计数不会依赖与gc

image-20210314163730589
在Python GC中将回收对象分成三个等级代龄,GEN0管理新加入的新生代,GEN1管理上次清理依然存活的中青代,GEN2是两次清理依然存活的生命周期极其漫长的对象,每一个等级代龄都有一个最大的阈值,每次 GEN0超出最大阈值都将会被回收

GC首先检查 GEN2如果超出阈值,则合并 GEN2 GEN1 GEN0追踪链表,如果没有超出链表,则检查 GEN1同时提升存活对象的代龄,而那些被回收的对象则存放在专门的列表中等待回收

包含__del__的对象永远不会被GC回收,直到进程终止

Copy
# !/usr/bin/env python3 # -*- coding: utf-8 -*- # @Time : 2021/3/11 下午7:13 # @Author : SR # @Email : srcoder@163.com # @File : test.py # @Software: PyCharm import gc print(gc.get_threshold()) # 获取每级的阈值 (700, 10, 10) print(gc.get_count()) # 获取追踪的对象数量 (558, 8, 0)

image-20210314165816948
GC对循环引用进行清除

Copy
# !/usr/bin/env python3 # -*- coding: utf-8 -*- # @Time : 2021/3/11 下午7:13 # @Author : SR # @Email : srcoder@163.com # @File : test.py # @Software: PyCharm import gc import weakref class User(object): pass def callback(x): print("will be dead %s" % x) a = User() b = User() dis = gc.disable() # 停止gc 查看引用计数能力 wa = weakref.ref(a, callback) wb = weakref.ref(b, callback) # 循环引用 a.b = b b.a = a #  删除引用 del a del b #  调用弱引用 wa() wb()

image-20210314174103479

Copy
# !/usr/bin/env python3 # -*- coding: utf-8 -*- # @Time : 2021/3/11 下午7:13 # @Author : SR # @Email : srcoder@163.com # @File : test.py # @Software: PyCharm able = gc.enable() # 开启gc机制 #  删除引用 del a del b #  调用弱引用 wa() wb()

image-20210314174813667
如果包含__del__则GC对循环引用无作用

Copy
# !/usr/bin/env python3 # -*- coding: utf-8 -*- # @Time : 2021/3/11 下午7:13 # @Author : SR # @Email : srcoder@163.com # @File : test.py # @Software: PyCharm import gc import weakref class User(object): def __del__(self): pass def callback(x): print('\n') print("will be dead %s" % x) res = gc.set_debug(gc.DEBUG_STATS | gc.DEBUG_LEAK) # 输出详细的回收信息 a = User() b = User() # dis = gc.disable() wa = weakref.ref(a, callback) wb = weakref.ref(b, callback) # 循环引用 a.b = b b.a = a #  删除引用 del a del b co = gc.collect() # 防止未达到阈值 手动回收 res1 = wa() res2 = wb()

image-20210314182231281

posted @   SR丶  阅读(93)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
点击右上角即可分享
微信分享提示
CONTENTS