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: PyCharmimport sysb = 10086print(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: PyCharmimport typesx = 10print(x.__class__) # 查看内置的类型print(type(x) is types.IntType) # Trueprint(x.__class__ is type(x) is types.IntType is int) # Truey = xprint(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: PyCharmx = 10print(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: PyCharmglobals()['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: PyCharmprint(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: PyCharmprint(locals() is globals()) # Trueprint(locals())print(ID(locals())) # 140543843598912print(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: PyCharmdef 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: PyCharma = object()b = aprint(a is b) # Trueprint(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: PyCharmimport copyl = [i * i for i in range(10)] # 生成一个新的列表l2 = copy.copy(l)print(l is l2) # Falseprint(ID(l)) # 140221885816416print(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: PyCharmimport copyl = [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: PyCharmimport copya = 'hello'b = copy.copy(a)print(a is b) # Trueprint(ID(a)) # 140178072833984print(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: PyCharmimport copya = ([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)) # 33803008print(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: PyCharmimport copyl = [i * 2 for i in range(10)]l1 = copy.deepcopy(l)print(l1)print(ID(l)) # 140562607271520print(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: PyCharmimport copyl = [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: PyCharmimport copyl = ([i for i in range(10)],)l1 = copy.deepcopy(l)print(ID(l)) # 140310375300368print(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: PyCharmimport copyl = ([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: PyCharmimport sysclass User(object): def __del__(self): print("计数为0 释放资源")user = User()print(sys.getrefcount(user)) # 2a = userprint(sys.getrefcount(user)) # 3del 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: PyCharmimport sysimport weakrefclass 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 # 删除原对象 会调用回调函数callbackprint(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: PyCharmimport gcclass User(object): def __del__(self): print(hex(ID(self)), 'will be dead')disable_gc = gc.disable() # 关闭gcuser = User() # 实例化一个对象print(hex(ID(user))) # 0x7fa46c869150del user # 回收对象,引用计数不会依赖与gc
在Python GC中将回收对象分成三个等级代龄,GEN0
管理新加入的新生代,GEN1
管理上次清理依然存活的中青代,GEN2
是两次清理依然存活的生命周期极其漫长的对象,每一个等级代龄都有一个最大的阈值,每次 GEN0
超出最大阈值都将会被回收
GC首先检查 GEN2
如果超出阈值,则合并 GEN2
GEN1
GEN0
追踪链表,如果没有超出链表,则检查 GEN1
同时提升存活对象的代龄,而那些被回收的对象则存放在专门的列表中等待回收
# !/usr/bin/env python3# -*- Coding: utf-8 -*-# @Time : 2021/3/11 下午7:13# @Author : SR# @Email : srcoder@163.com# @file : test.py# @Software: PyCharmimport gcprint(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: PyCharmimport gcimport weakrefclass User(object): passdef 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 = bb.a = a# 删除引用del adel 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: PyCharmable = gc.enable() # 开启gc机制# 删除引用del adel b# 调用弱引用wa()wb()
包含__del__
的对象永远不会被GC
回收,直到进程终止
以上是内存溢出为你收集整理的Python运行全部内容,希望文章能够帮你解决Python运行所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)