Python仮想マシンのバイトコードのデコレータを実装する方法

WBOY
リリース: 2023-05-04 08:31:06
転載
840 人が閲覧しました

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

この命令は、スタックの先頭に 2 つの値を加算し、結果をスタックにプッシュするために使用されます。

>>> dis.dis(lambda: x + y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 BINARY_ADD
              6 RETURN_VALUE
ログイン後にコピー

BINARY_SUBTRACT

この命令は、スタックの先頭にある 2 つの値を減算し、結果をスタックにプッシュするために使用されます。

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

この命令は 2 つの値を比較するために使用されます。スタックの最上部に配置され、比較の結果がスタックにプッシュされます。このバイトコードの後の次のバイトのパラメータは、「より小さい」、「より大きい」、「等しくない」などの比較記号を表します。例:

>>> 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
ログイン後にコピー

バイトコードの観点から見たデコレータの原理の分析

あなたが Python を使用している場合は、多かれ少なかれデコレータについて聞いたことがあるはずです。これは、Python の糖衣構文であり、次のことができます。これを使用すると、時間の計算など、ソース コードを変更せずに関数に関数を追加するなど、多くの興味深いことを行うことができます。

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)

Achieved が表示されます。私たちが望んでいた効果。

ここで、より簡単な例を使用して上記のコード構造をシミュレートし、上記の関数の実行プロセスを分析できるようにします。

s = """
def decorator(func):
    print("Hello")
    return func
 
@decorator
def fib(n):
    pass
"""
dis.dis(s)
ログイン後にコピー

上記の dis 関数の出力はバイトに対応します。コードの内容 コードは次のとおりです。

2 0 LOAD_CONST 0 (<コード オブジェクト デコレータ 0x108068d40、ファイル ""、行 2>)
2 LOAD_CONST 1 ('デコレータ')
4 MAKE_FUNCTION 0
6 STORE_NAME 0 (デコレータ)

6 8 LOAD_NAME 0 (デコレータ)

7 10 LOAD_CONST 2 (< ; コード オブジェクト fib 0x1075c1710、ファイル ""、行 6> )
12 LOAD_CONST 3 ('fib')
14 MAKE_FUNCTION 0
16 CALL_FUNCTION 1
18 STORE_NAME 1 (fib)
20 LOAD_CONST 4 (なし)
22 RETURN_VALUE

<コード オブジェクト デコレーターの逆アセンブリ (0x108068d40、ファイル ""、行 2>):
3 0 LOAD_GLOBAL 0 (print)
2 LOAD_CONST 1 (「こんにちは」)
4 CALL_FUNCTION 1
6 POP_TOP

4 8 LOAD_FAST 0 (func)
10 RETURN_VALUE

」、行6>:
8 0 LOAD_CONST 0 (なし)
2 RETURN_VALUE

第一条命令 LOAD_CONST を実行、この条命令は主にコード オブジェクトをダウンロードします。関数デコレータを含む文字コードは、主に上の文字コードの 2 番目のブロックの内容です。この文字コードの実行後のスペースは次のようになります。 2 番目の命令 LOAD_CONST を実行した後、文字列デコレータがスペースに追加されます。

Python仮想マシンのバイトコードのデコレータを実装する方法

3 番目の命令 MAKE_FUNCTION を実行します。この文字コードの機能は仮想マシンの内部で関数が作成されます。関数の名前はデコレータです。関数に対応する文字コードは、以前に入力空間に圧縮されていたコード オブジェクト オブジェクトであり、この命令はまた、適切な関数オブジェクトを入力空間に圧縮します。

Python仮想マシンのバイトコードのデコレータを実装する方法

STORE_NAME、条文字コードは、重要な要素を取り出し、co_names[oparg] をこのオブジェクトに向け、上の文字コード当中に co_names[oparg] を指定します。つまり、デコレータです。

Python仮想マシンのバイトコードのデコレータを実装する方法

LOAD_NAME、この文字コードは、co_names[oparg] に対応する名前が指定されているオブジェクトを再び空間に追加します。つまり、上のデコレータ関数です。

Python仮想マシンのバイトコードのデコレータを実装する方法

#次の 3 文字のコード LOAD_CONST、LOAD_CONST、および MAKE_FUNCTION を実行すると、スペースは次のようになります。 ##################################

接下来的一条指令非常重要,这条指令便是装饰器的核心原理,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 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:yisu.com
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!