当前位置: 首页 > 网络应用技术

python计数器 - 专用字节代码

时间:2023-03-08 01:55:22 网络应用技术

  如果您已经编写或使用了Python,则可能已经习惯了查看Python源代码文件;他们的名称以.py.的结尾。-Directory称为Pycache。)。PYC文件可以防止每次运行时python重新分析源代码,从而大大节省了文件。

  Python通常被描述为一种解释语言。在此语言中,您的源代码在运行程序时被翻译成CPU指令,但这是正确的。就像许多解释的语言一样,Python实际上将源代码编译为虚拟机的一组指令。Python解释器是虚拟机的实现。此中间格式称为“字节码”。

  因此,Python留下的这些.PYC文件是为了使运行速度“更快”或源代码的“优化”版本;它们是在Python Virtual MachineStruction上运行的字节代码。

  CPYTHON使用基于堆栈的虚拟机。换句话说,它完全围绕堆栈数据结构(您可以将项目“将”推到结构的“顶部”,或“ pop”项目“ pop”到“ top”)。Cpython使用三种类型的堆栈:

  1.调用堆栈。这是操作中Python程序的主要结构。对于每个当前活动的函数调用,它具有一个项目的“帧”,堆栈的底部是程序的入口点.EATH功能调用将将新框架推向呼叫堆栈。每个函数调用返回,其框架将弹出

  2.在每个帧中,都有一个评估堆栈(也称为数据堆栈)。此堆栈是Python函数执行Python函数的地方。Python代码主要包括将事物推入此堆栈,操纵它们,然后弹出。

  3.同样在每个帧中,都有一个堆栈。Python使用它来跟踪某些类型的控制结构:循环,尝试 /块除外,并且有块可能会导致条目被推到阻塞堆栈。每当这些结构之一中的一个,块堆栈都会弹出。这有助于Python知道在任何给定时间的哪个块是活动,例如,继续或断开语句可能会影响正确的块。

  大多数Python字节码指令都操作当前调用堆栈帧的计算堆栈。尽管有一些说明可以做其他事情(例如跳到指定的说明或操作块堆栈)。

  为了更好地理解,假设我们有一些调用该功能的代码,例如:

  Python将转换为一系列字节码指令:1。一个LOAD_NAME指令用于查找函数对象MY_FUNCTICT并将其推到计算堆栈的顶部。

  2.另一个load_name指令查找变量my_variable并将其推到计算堆栈的顶部

  3.一个LOAD_CONST指令将整数2推到计算堆栈的顶部

  4.一个call_funch指令call_function指令有两个参数,这表明Python需要在堆栈顶部弹出两个位置参数;然后将在其上调用该函数,并同时弹出函数(关键字参数的函数,使用指令call_function_kw-相似的操作,并使用第三个指令来call_function_ex,适用于涉及参数的函数调用使用*或**运算符)一旦Python拥有这些,它将在调用堆栈上分配一个新帧。,填充函数调用的本地变量,然后在框架中运行my_function的字节代码,将从呼叫堆栈中弹出框架。在原始帧中,MY_Function的返回值将被推到计算堆栈的顶部。

  我们知道这件事,我们也知道字节代码具有文件,但是如何使用字节代码?确定不知道。没关系。下次,我们将重点关注字节代码。Python中有一个模块可以通过编译Python代码来生成字节代码来生成字节代码。

  DIS模块包含一些用于处理Python字节码的功能,可以将字节代码“编译”到更方便的读取。查看解释器运行的字节码还有助于优化代码。此模块也有助于在多线程中找到竞争条件,因为它可以用于评估代码中的哪个线程控制可能会切换。引用到源代码include/opcode.h,您可以找到字节代码的官方列表。您可以详细查看官方文档。注意由不同版本生成的Python生成的字节代码的内容可能有所不同。在这里,我使用Python 3.8。

  输入以下内容并运行它:

  函数dis.dis()将编译函数,方法,类,模块,编译Python代码对象或字符串中包含的源代码,并显示人类可读的版本。在dis Module中的其他方便函数是distb()。可以将其传递给Python可追溯的对象,或者在发生预期情况时调用它,然后在发生预期的外部情况并显示其字节代码并插入一个时,它将在反行期堆栈顶部使用该功能在一个方面,指向指示引起事故的指令。

  它还可以用来查看Python为每个函数构建的编译代码对象,因为运行一个函数将使用这些代码对象的属性。此外,它是查看Hello()函数:

  可以在功能代码中访问代码对象,并带有一些重要属性:

  co_consts是一个命运组co_varnames,在功能正文中的任何实际数字中都存在。将加载值推向堆栈,或这些金属组中的变量和属性索引中的存储值作为其参数。

  因此,现在我们可以理解Hello()函数中列出的字节代码:

  1. LOAD_GLOBAL 0:告诉Python查找由索引0通过CO_NAME指向的总体对象(这是打印功能),然后将其推到计算堆栈

  2. LOAD_CONST 1:将co_consts在索引1上的文字值提出返回表达式,返回此隐藏值)。

  3. call_function 1:告诉Python调用功能;它需要从堆栈中弹出一个位置参数,然后将通过函数调用新的堆栈顶部。“原始”字节码 - IS非人类可读格式can也可以作为代码对象上的co_code属性可用。如果您有兴趣尝试手动编译功能,则可以使用小数字节值的小数点字节值来检查字节代码指令的名称。

  函数dis()可以打印python源代码的计数器汇编(模块,类,方法,功能或代码对象)。您可以使用命令行来运行诸如dis_simple.py之类的模块。

  输出由列组织,包括原始源代码行号,代码对象中的指令地址,操作代码名称和传递给操作代码的任何参数。对于简单代码,我们可以以表单执行以下命令命令行:

  输出

  源代码将转换为四个不同的操作以创建和填充字典,然后将结果保存到本地变量。首先说明每个行参数的含义:以第一个指令为例:一个示例:

  第一列号(1)表示相应源代码的行数。第二列(可选)指示当前执行指令(例如,当字节代码来自帧对象时)[此示例不是]第三列是标签,这意味着此可能的跳跃的先前指令[此示例不是]第四列号是对应于字节码中字节索引对应的地址(这些是2的倍数,因为每个指令使用Python 3.62个字节,并且在上一个版本中可能有不同的位置。与第五列指令本身相对应的人类可读名称的名称是第六列Python中“ LOAD_CONST”指令的参数,用于获得某些常数或某些常数或变量,管理堆栈,跳到特定的说明等。(如果是),则在第七列中计算出的实际参数。

  然后让我们看一下这个过程:由于Python解释器基于堆栈,因此使用load_const以正确顺序将前几个步骤放在堆栈中,然后将字典的新键和值添加到字典中.us store_name将dict对象绑定到my_dict。

  应该注意的是,上述命令行是编译以编译的,并且我们不能自动递归反编写功能,因此我们需要使用在文件中导入DIS的模式进行反复处理,就像以下内容一样。

  运行命令

  然后获得以下结果

  要查看内部函数,必须将函数传递到dis()。由于内部事物在这里打印,因此未显示函数外层的线号,但它从2开始。

  以下每条指令的含义:

  1. LOAD_GLOBAL用于加载全局变量,包括指定的函数名称,类名称,模块名称和其他全局符号。这是LEN函数。LOAD_FAST通常加载局部变量的值,即读取值,用于计算或函数调用传递参数任务,这是参数args。

  2.通常,首先调用的函数,然后按参数,最后通过call_function调用。

  3. Store_fast将值保存到本地变量。也就是说,将结果分配给store_fast。

  4.下面的打印由于两个参数,load_fast具有两次proctint。

  5.最后,通过return_value确定函数的功能。

  要打印函数的摘要信息,我们可以使用DIS的show_code方法。它包含名称的参数和相关信息。show_code的参数是此函数对象。代码如下:

  运行后,结果如下

  您可以看到返回的内容具有功能,方法,参数和其他信息。

  我们知道如何编译功能的内部,并且还可以以相似的方法编译类。LET请参见一个示例:

  操作的总和是以下结果

  从总体内容的角度来看,结果分为INIT和STR拆卸的两个部分。

  1.然后您需要注意该方法按字母的顺序列出,因此在部分中,首先查看名称并查看自我,但它们都是load_fast。

  2. store_attr self.name = name。

  3.然后load_const a none和return_value标记函数的末尾。

  接下来分析str部分:

  1. load_const已加载'MyObject({})'到堆栈

  2.然后通过load_method调用字符串格式方法。此方法在Python3.7中新可用。

  3. load_fast也是自我。

  4. LOAD_ATTR通常是当方法调用某个对象时。此是self.name。名称操作

  5. call_method是python3.7的新添加的内容。这是执行方法。

  6. return_value表示函数的末尾。上述字符串使用格式的缝合。我一直都建议使用F弦。让我们通过字节码对其进行分析,为什么F-string比格式更快。

  代码其他代码没有变化,返回已更改为以下内容:

  再次执行,让我们看一下str函数的部分。STR:9 0 load_const 1('myObject('MyObject(')2 load_fast 0(self)4 load_attr 0(name)6 format_value 0 8 load_const 2('concst 2('load_const 2)2('))10 build_string 3 12 return_value.method,通过使用format_value指令将FSTRING替换为FSTRING。为什么格式的功能慢,python中的函数调用的费用可观。当使用str.format()时,在call_method中花费的额外时间比FSTRING慢得多。

  在调试异常时,有时您需要检查哪个字节。这次,这非常有用。有很多方法可以通过错误的代码编译代码。第一个策略是使用dis()报告交互式解释器中的最后一个例外。如果没有参数(),它将找到一个例外并显示导致这种异常的顶部元素的汇编效果。

  打开我的命令行以执行如下:

  线号的后一个数字是导致错误的操作代码,即LOAD_NAME指令,因为没有定义变量i,因此与此名称关联的值无法加载到堆栈中。

  该程序还可以打印有关事件追溯的相关信息,并将其传递给distb()方法。

  以下程序中存在分裂的异常。但是该公式有两个分区,因此尚不清楚它的哪一部分是错误的。目前,我们可以使用以下方法:dis_traceback.py

  运行后输出

  结果,在字节代码中反映的字节码很长。我们不需要全部阅读。请参阅初始外观 - >您可以知道错误的位置。setup_finally bytecode的含义是将try块从try-except子句推入堆栈中。可以在此处看到load_name将load_name按j压入堆栈并报告错误因此,可以推断(I/J)是错误的。

  https://docs.python.org/zh-cn/3.7/library/dis.html#opcode-store_fast

  https://opensource.com/article/18/4/introduction-python-decode

  https://hackernoon.com/a-closer-how-how-python-fython-strings-work-f19773b3bdbbdb