>백엔드 개발 >파이썬 튜토리얼 >Python 가상 머신 바이트코드의 데코레이터를 구현하는 방법

Python 가상 머신 바이트코드의 데코레이터를 구현하는 방법

WBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWB
WBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWB앞으로
2023-05-04 08:31:06932검색

Python 공통 바이트코드

LOAD_CONST

이 명령어는 스택에 상수를 로드하는 데 사용됩니다. 상수는 숫자, 문자열, 튜플, 목록, 사전 등과 같은 객체일 수 있습니다. 예:

>>> dis.dis(lambda: 42)
  1           0 LOAD_CONST               1 (42)
              2 RETURN_VALUE

LOAD_NAME

이 명령어는 변수를 스택에 로드하는 데 사용됩니다. 예:

>>> dis.dis(lambda: x)
  1           0 LOAD_GLOBAL              0 (x)
              2 RETURN_VALUE
>>>

STORE_NAME

이 명령어는 스택 상단의 값을 변수에 저장하는 데 사용됩니다. 예:

>>> dis.dis("x=42")
  1           0 LOAD_CONST               0 (42)
              2 STORE_NAME               0 (x)
              4 LOAD_CONST               1 (None)
              6 RETURN_VALUE

BINARY_ADD

이 명령어는 스택 상단에 두 값을 추가하고 결과를 스택에 푸시하는 데 사용됩니다.

>>> dis.dis(lambda: x + y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 BINARY_ADD
              6 RETURN_VALUE

BINARY_SUBTRACT

이 명령어는 스택 맨 위의 두 값을 빼고 결과를 스택에 푸시하는 데 사용됩니다.

>>> dis.dis(lambda: x - y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 BINARY_SUBTRACT
              6 RETURN_VALUE

나머지를 얻기 위한 덧셈, 뺄셈, 곱셈, 나눗셈에 대한 동일한 바이트 코드는 다음과 같습니다.

>>> dis.dis(lambda: x + y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 BINARY_ADD
              6 RETURN_VALUE
>>> dis.dis(lambda: x - y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 BINARY_SUBTRACT
              6 RETURN_VALUE
>>> dis.dis(lambda: x * y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 BINARY_MULTIPLY
              6 RETURN_VALUE
>>> dis.dis(lambda: x / y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 BINARY_TRUE_DIVIDE
              6 RETURN_VALUE
>>> dis.dis(lambda: x // y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 BINARY_FLOOR_DIVIDE
              6 RETURN_VALUE
>>> dis.dis(lambda: x % y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 BINARY_MODULO
              6 RETURN_VALUE

COMPARE_OP

이 명령어는 스택 상단의 두 값을 비교하고 그 결과를 푸시하는 데 사용됩니다. 이 바이트는 코드 뒤의 마지막 바이트의 매개변수보다 작음, 보다 큼, 같지 않음 등의 비교 기호를 나타냅니다. 예:

>>> dis.dis(lambda: x - y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 BINARY_SUBTRACT
              6 RETURN_VALUE
>>> dis.dis(lambda: x > y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 COMPARE_OP               4 (>)
              6 RETURN_VALUE
>>> dis.dis(lambda: x < y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 COMPARE_OP               0 (<)
              6 RETURN_VALUE
>>> dis.dis(lambda: x != y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 COMPARE_OP               3 (!=)
              6 RETURN_VALUE
>>> dis.dis(lambda: x <= y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 COMPARE_OP               1 (<=)
              6 RETURN_VALUE
>>> dis.dis(lambda: x >= y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 COMPARE_OP               5 (>=)
              6 RETURN_VALUE
>>> dis.dis(lambda: x == y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 COMPARE_OP               2 (==)
              6 RETURN_VALUE

RETURN_VALUE

스택의 최상위 요소를 반환 값으로 팝합니다.

BUILD_LIST

이 명령은 목록을 만드는 데 사용됩니다. 예:

>>> dis.dis(lambda: [a, b, c, e])
  1           0 LOAD_GLOBAL              0 (a)
              2 LOAD_GLOBAL              1 (b)
              4 LOAD_GLOBAL              2 (c)
              6 LOAD_GLOBAL              3 (e)
              8 BUILD_LIST               4
             10 RETURN_VALUE

이 바이트코드 명령어에는 스택 공간의 목록 요소 수를 나타내는 매개변수가 있습니다. 위 예에서 이 매개변수는 4입니다.

BUILD_TUPLE

이 명령은 튜플을 만드는 데 사용됩니다. 예:

>>> dis.dis(lambda: (a, b, c))
  1           0 LOAD_GLOBAL              0 (a)
              2 LOAD_GLOBAL              1 (b)
              4 LOAD_GLOBAL              2 (c)
              6 BUILD_TUPLE              3
              8 RETURN_VALUE

동일한 바이트코드에는 튜플을 생성하기 위한 요소 수를 나타내는 매개변수도 있습니다.

BUILD_MAP

이 명령은 사전을 만드는 데 사용됩니다. 예:

BUILD_SET

리스트 및 튜플과 마찬가지로 이 명령어는 컬렉션 개체를 만드는 데 사용됩니다. 동일한 명령어에는 컬렉션을 만드는 데 사용되는 요소 수를 나타내는 매개 변수도 있습니다.

>>> dis.dis(lambda: {a, b, c, d})
  1           0 LOAD_GLOBAL              0 (a)
              2 LOAD_GLOBAL              1 (b)
              4 LOAD_GLOBAL              2 (c)
              6 LOAD_GLOBAL              3 (d)
              8 BUILD_SET                4
             10 RETURN_VALUE

BUILD_CONST_KEY_MAP

이 명령은 사전 개체를 만드는 데 사용됩니다. 동일한 명령에는 사전의 요소 수를 나타내는 매개 변수도 있습니다.

>>> dis.dis(lambda: {1:2, 3:4})
  1           0 LOAD_CONST               1 (2)
              2 LOAD_CONST               2 (4)
              4 LOAD_CONST               3 ((1, 3))
              6 BUILD_CONST_KEY_MAP      2
              8 RETURN_VALUE

바이트코드 관점에서 본 데코레이터 원리 분석

파이썬 전문가라면 데코레이터에 대해 들어본 적이 있을 것입니다. 이것은 파이썬을 사용하여 많은 흥미로운 작업을 수행할 수 있는 구문입니다. , 예를 들어 시간 계산과 같이 소스 코드를 수정하지 않고 함수에 일부 기능을 추가하는 등의 작업을 수행합니다.

import time
 
def eval_time(func):
    
    def cal_time(*args, **kwargs):
        start = time.time()
        r = func(*args, **kwargs)
        end = time.time()
        return r, end - start
    return cal_time
 
 
@eval_time
def fib(n):
    a = 0
    b = 1
    while n > 0:
        n -= 1
        a, b = b, a + b
    return a

위 코드에서는 피보나치 수열을 계산하는 함수를 구현했습니다. 또한 함수의 실행 시간을 계산하는 eval_time 함수도 작성했습니다. 이제 프로그램의 출력인 fib(10) 함수를 호출합니다. 아래와 같이:

>>>fib(10)
(55, 5.9604644775390625e-06)

우리가 원하는 효과가 달성된 것을 확인할 수 있습니다.

이제 위 코드 구조를 시뮬레이션하기 위해 더 간단한 예를 사용하여 위 함수의 실행 프로세스를 분석할 수 있습니다.

s = """
def decorator(func):
    print("Hello")
    return func
 
@decorator
def fib(n):
    pass
"""
dis.dis(s)

위 dis 함수의 출력에 해당하는 바이트코드는 다음과 같습니다.

  2           0 LOAD_CONST               0 (", 라인 2>)
              2 LOAD_CONST              1 ('데코레이터')
              4 MAKE_FUNCTION            0
              6 STORE_NAME               0 (데코레이터)
 
  6 8 LOAD_NAME                0 (데코레이터)
 
  7          10 LOAD_CONST               2 (", 6행>)
     이
             18 STORE_NAME               1 (fib)
             20 LOAD_CONST               4 (없음)
             22 RETURN_VALUE
 
", 라인 2>의 디스어셈블리:
  3           0 LOAD_GLOBAL              0(인쇄)
이               0 (func)
             10 RETURN_VALUE
 
", 6행> :
  8           0 LOAD_CONST               0 (없음)
              2 RETURN_VALUE


执行第一条指令 LOAD_CONST,这条指令主要是加载一个 코드 객체 对象,这个对象里face主要是包含函数 데코레이터 字节码,主要是上上字节码的第二块内容。 에서 执行完这条字节码之后栈空间如下所示:


执行完第二条指令 LOAD_CONST 后,会将字符串 데코레이터 加载进入栈空间当中。

Python 가상 머신 바이트코드의 데코레이터를 구현하는 방법

执行第三条指令 MAKE_FUNCTION,这条字节码的작용是在虚拟机内part创建一个函数,函数의 이름称为 데코레이터, 函数对应的字节码则是在先前压入栈空间当中의 코드 객체对象,这条指令还会将创建好的函数对象压入栈中。

Python 가상 머신 바이트코드의 데코레이터를 구현하는 방법

STORE_NAME,条字节码会将栈顶的函数对象压入栈中,并且将 co_names[oparg] 指向这个对象, 위 화면에 있음字节码当中 co_names[oparg] 就是 데코레이터.栈空间当中,也就是上面的 decorator 函数加入进行栈空间当中。

Python 가상 머신 바이트코드의 데코레이터를 구현하는 방법

接下来tive 3条字节码 LOAD_CONST, LOAD_CONST 와 MAKE_FUNCTION, 에서 执行这 3条字节码 后,栈空间如下所示:

Python 가상 머신 바이트코드의 데코레이터를 구현하는 방법

接下来的一条指令非常重要,这条指令便是装饰器的核心原理,CALL_FUNCTION 这条指令有一个参数 i,在上面的字节码当中为 1,也就是说从栈顶开始的前 i 个元素都是函数参数,调用的函数在栈空间的位置为 i + 1 (从栈顶往下数),那么在上面的情况下就是说调用 decorator 函数,并且将 fib 函数作为 decorator 函数的参数,decorator 函数的返回值再压入栈顶。在上面的代码当中 decorator 函数返回值也是一个函数,也就是 decorator 函数的参数,即 fib 函数。

Python 가상 머신 바이트코드의 데코레이터를 구현하는 방법

接下来便是 STORE_NAME 字节码,这条字节码的含义我们在前面已经说过了,就是将栈顶元素弹出,保存到 co_names[oparg] 指向的对象当中,在上面的代码当中也就是将栈顶的对象保存到 fib 当中。栈顶元素 fib 函数是调用函数 decorator 的返回值。

看到这里就能够理解了原来装饰器的最根本的原理不就是函数调用嘛,比如我们最前面的用于计算函数执行时间的装饰器的原理就是:

fib = eval_time(fib)

将 fib 函数作为 eval_time 函数的参数,再将这个函数的返回值保存到 fib 当中,当然这个对象必须是可调用的,不然后面使用 fib() 就会保存,我们可以使用下面的代码来验证这个效果。

def decorator(func):
    return func()
 
 
@decorator
def demo():
    return "function demo return string : Demo"
 
print(demo)

执行上面的程序结果为:

function demo return string : Demo

可以看到 demo 已经变成了一个字符串对象而不再是一个函数了,因为 demo = decorator(demo),而在函数 decorator 当中返回值是 demo 函数自己的返回值,因此才打印了字符串。

위 내용은 Python 가상 머신 바이트코드의 데코레이터를 구현하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 yisu.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제