Python运行浅析

2021-06-08 06:05

阅读:386

标签:改变   types   target   代码执行   software   分割   value   soft   har   

运行环境

代码执行

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文件
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‘: }
    
    print(globals()) # {‘test‘: }	函数外部可以定义函数的名字空间


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()

技术图片

Python运行浅析

标签:改变   types   target   代码执行   software   分割   value   soft   har   

原文地址:https://www.cnblogs.com/SR-Program/p/14533563.html


评论


亲,登录后才可以留言!