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文件运行过程
- python会将
py
文件边写成字节码文件 - 将编译之后的字节码文件传递给虚拟机
- 虚拟机会从编译之后的
PyCodeObject
得到字节码指令且执行该指令等
.Pyc文件
.pyc
文件是字节码在磁盘上的表现- 执行
python test.py
会将该文件编译成字节码但是并不会生成.pyc
文件 - 如果
test.py
文件导入模块例如re
模块此时python会对re
进行编译成字节码文件 生成re.pyc
文件然后对字节码进行解释执行 - 如果想生成
test.pyc
文件可以使用内置的模块py_compile
也可以使用python -m test.py
- 加载模块时候如果同时存在
.pyc
与.py
文件则会使用.pyc
运行 如果.pyc
的编译时间早于.py
的编译时间则会执行.py
文件并且更新.pyc
文件
python -m test.py
类型和对象
先有类型(Type),而后才能生成实例(instance),Python中一切都是对象,包括实例内的对象都包含一个标准头,通过头部信息既可以知道明确的数据类型
头部信息由引用计数和类型指针组成,前者在对象被引用的时候计数增加,当超出作用域或者被释放的时候计数减小,等于0的时候会被虚拟机回收(某些被缓存的对象计数器永远不会为0)
引用计数
# !/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))
类型指针
类型指针指向具体的类型对象,其中包含了继承关系,静态成员信息等,所有的内置数据类型都可以从types模块中找到
# !/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)) # 同一类型 内存地址一样
名字空间
x = 10 # 我们习惯将x称为变量 但是准确来说其应该称为名字
Python的名字实际上是一个字符串对象,其和所对应的值一起在名字空间中构造成一项 {"name":"object"}
关联
Python有多种名字空间,被称之为 globals
的模块名字空间,被称之为 locals
的函数堆栈名字空间,不同的名字空间决定了对象的作用域以及生存周期
# !/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')) # 字典取值
从上述结果展示可以看出名字空间即为一个字典对象,可以通过字典创建 key:value
键值对的方法对名字空间中添加名字
# !/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')) # 获取名字空间所对应的值
名字的作用仅仅是在某个时刻将名字空间内的某个对象进行关联,其本身并不包含任何对象信息,只有通过对象的头部信息才能获知晓相关的数据类型,进而进行相应的数据查找,因为名字的弱类型特征,我们可以在程序的运行过程之中,随时更改其数据类型
# !/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'])
在函数外部 locals()
与 globals()
作用完全相同,但是在函数内部 locals()
则是获取当前函数的名字空间,其中存储的是函数参数,局部变量信息等
# !/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
# !/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没有值类型与引用类型
# !/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)
深浅拷贝
浅拷贝
浅拷贝是对一个对象父级(外层)拷贝,不会拷贝(内部)子级
父级对象为可变类型
- 当外层为可变对象的时候复制生成的对象会开辟新的地址空间
- 外层为可变对象原值修改不会影响拷贝之后的值
# !/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
# !/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]
父级对象为不可变类型
- 当外层对象为不可变类型的时候复制之后的对象会引用原对象地址
- 外层对象为不可变类型的时候原值改变复制之后的值也会改变
# !/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
# !/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
深拷贝
深拷贝是对对象内部外部都进行拷贝(递归)
父级对象为可变对象
- 外层对象为可变对象的时候无论内部为什么数据类型都会开辟新的内存空间
- 外层对象为可变对象的时候原值改变不会影响复制之后的值
# !/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
# !/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))
父级对象为不可变对象
- 外层对象为不可变对象内层为可变对象会开辟新的内存地址
- 外层对象为不可边对象内层为可变对象的时候原值修改不会影响复制之后的值
# !/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
# !/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],)
垃圾回收
引用计数
Python默认使用引用计数,对象被引用计数会增加一次,当对象计数为0的时候,该对象内存资源会被回收,要么所对应的block块被置为空闲要么将资源返回给操作系统
# !/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 # 删除最后一个引用
某些内置类型,例如小整数由于缓存原因计数永远不会为0,直到进程结束由虚拟机调用释放函数进行清理
除了直接引用,Python还支持弱引用,允许在不增加计数,不影响释放的情况下间接引用对象,但不是所有数据类型都支持若引用,例如列表,字典等
# !/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
分代清除
Python除引用计数之外,还有专门处理循环的GC
引发循环引用问题的一般都是容器类对象,例如 list
set
object
等,对于该类容器对象,一般都是由 GC
管理,但是并不是说此类对象就是有 GC
进行管理,当没有发生循环引用的时候还是由积极性更高的计数器进行回收
# !/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
在Python GC中将回收对象分成三个等级代龄,GEN0
管理新加入的新生代,GEN1
管理上次清理依然存活的中青代,GEN2
是两次清理依然存活的生命周期极其漫长的对象,每一个等级代龄都有一个最大的阈值,每次 GEN0
超出最大阈值都将会被回收
GC首先检查 GEN2
如果超出阈值,则合并 GEN2
GEN1
GEN0
追踪链表,如果没有超出链表,则检查 GEN1
同时提升存活对象的代龄,而那些被回收的对象则存放在专门的列表中等待回收
包含__del__
的对象永远不会被GC
回收,直到进程终止
# !/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)
GC
对循环引用进行清除
# !/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()
# !/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()
如果包含__del__
则GC对循环引用无作用
# !/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()