Maison > développement back-end > Tutoriel Python > Comment implémenter le décorateur du bytecode de la machine virtuelle Python

Comment implémenter le décorateur du bytecode de la machine virtuelle Python

WBOY
Libérer: 2023-05-04 08:31:06
avant
879 Les gens l'ont consulté

Python Common Bytecode

LOAD_CONST

Cette instruction est utilisée pour charger une constante sur la pile. Les constantes peuvent être des objets tels que des nombres, des chaînes, des tuples, des listes, des dictionnaires, etc. Par exemple :

>>> dis.dis(lambda: 42)
  1           0 LOAD_CONST               1 (42)
              2 RETURN_VALUE
Copier après la connexion

LOAD_NAME

Cette instruction permet de charger une variable dans la pile. Par exemple :

>>> dis.dis(lambda: x)
  1           0 LOAD_GLOBAL              0 (x)
              2 RETURN_VALUE
>>>
Copier après la connexion

STORE_NAME

Cette instruction est utilisée pour stocker la valeur en haut de la pile dans une variable. Par exemple :

>>> dis.dis("x=42")
  1           0 LOAD_CONST               0 (42)
              2 STORE_NAME               0 (x)
              4 LOAD_CONST               1 (None)
              6 RETURN_VALUE
Copier après la connexion

BINARY_ADD

Cette instruction permet d'ajouter les deux valeurs en haut de la pile et de pousser le résultat vers la pile.

>>> dis.dis(lambda: x + y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 BINARY_ADD
              6 RETURN_VALUE
Copier après la connexion

BINARY_SUBTRACT

Cette instruction est utilisée pour soustraire les deux valeurs​​en haut de la pile et pousser le résultat vers la pile.

>>> dis.dis(lambda: x - y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 BINARY_SUBTRACT
              6 RETURN_VALUE
Copier après la connexion

Le même bytecode pour l'addition, la soustraction, la multiplication et la division pour obtenir le reste est le suivant :

>>> 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
Copier après la connexion

COMPARE_OP

Cette instruction est utilisée pour comparer le haut de la pile Deux valeurs et pousser le résultat de la comparaison sur la pile. Le paramètre de l'octet suivant après ce bytecode représente le symbole de comparaison inférieur à, supérieur à, non égal à, etc. Par exemple :

>>> 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
Copier après la connexion

RETURN_VALUE

Insérez l'élément supérieur de la pile comme valeur de retour.

BUILD_LIST

Cette commande permet de créer une liste. Par exemple :

>>> 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
Copier après la connexion

Cette instruction de bytecode a un paramètre indiquant le nombre d'éléments de liste dans l'espace de pile. Dans l'exemple ci-dessus, ce paramètre est 4.

BUILD_TUPLE

Cette commande permet de créer un tuple. Par exemple :

>>> 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
Copier après la connexion

Le même bytecode a également un paramètre, indiquant le nombre d'éléments pour créer un tuple.

BUILD_MAP

Cette commande permet de créer un dictionnaire. Par exemple :

BUILD_SET

Comme la liste et le tuple, cette instruction permet de créer un objet de collection De même, cette instruction possède également un paramètre indiquant la valeur utilisée pour créer un objet de collection. collection. Le nombre d’éléments.

>>> 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
Copier après la connexion

BUILD_CONST_KEY_MAP

Cette commande est utilisée pour créer un objet dictionnaire. La même commande a également un paramètre, indiquant le nombre d'éléments dans le dictionnaire.

>>> 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
Copier après la connexion

Analyse du principe des décorateurs du point de vue du bytecode

Si vous êtes un pythoneur, alors vous avez dû plus ou moins entendre parler des décorateurs, c'est un python On peut utilisez du sucre syntaxique pour faire beaucoup de choses intéressantes, comme ajouter certaines fonctions aux fonctions sans modifier le code source, comme calculer le temps.

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
Copier après la connexion

Dans le code ci-dessus, nous avons implémenté une fonction pour calculer la séquence de Fibonacci. De plus, nous avons également écrit une fonction eval_time pour calculer le temps d'exécution de la fonction. le résultat du programme est le suivant :

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

#🎜 🎜#
Vous pouvez voir que l'effet souhaité est atteint.

Maintenant, nous utilisons un exemple plus simple pour simuler la structure de code ci-dessus, afin que nous puissions analyser le processus d'exécution de la fonction ci-dessus :

s = """
def decorator(func):
    print("Hello")
    return func
 
@decorator
def fib(n):
    pass
"""
dis.dis(s)
Copier après la connexion

La correspondance de sortie du dis ci-dessus function Le bytecode du code est le suivant :

  2           0 LOAD_CONST               0 (", ligne 2>)
              2 LOAD_CONST              1 ('décorateur')
              4 MAKE_FUNCTION            0
              6 STORE_NAME               0 (décorateur)
 
  6 8 LOAD_NAME                0 (décorateur)
 
  7          10 LOAD_CONST               2 (", ligne 6>)
          12 LOAD_CONST               3 ('fib')
             14 MAKE_FUNCTION            0
             16 CALL_FUNCTION            1
            18 STORE_NAME               1 (fib)
             20 LOAD_CONST               4 (Aucun)
             22 RETURN_VALUE
 
Désassembler y du ", ligne 2>:
  3           0 LOAD_GLOBAL              0 (print)
               0 (func)
             10 RETURN_VALUE
 
Démontage de l'objet ", ligne 6> :
  8           0 LOAD_CONST               0 (Aucun)
              2 RETURN_VALUE


执行第一条指令 LOAD_CONST,这条指令主要是加载一个 code object 对象,这个对象里面主要是包含函数 decorator 的字节码,主要是上面字Description du produit令 LOAD_CONST 之后,会将字符串 décorateur 加载进入栈空间当中。

MAKE_FUNCTION décorateur, objet de code条字节码会将栈顶砚元紴弹出,并且将 co_names[oparg] 指向这个对象,在上面的字节码当中 co_names[oparg] et décorateur.重新加载进入栈空间当中,也就是上面的 décorateur 函数加入进行栈空间当中。

Comment implémenter le décorateur du bytecode de la machine virtuelle Python接下来的三条字节码 LOAD_CONST,LOAD_CONST et MAKE_FUNCTION,空间如下所示:

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

Comment implémenter le décorateur du bytecode de la machine virtuelle Python

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

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

fib = eval_time(fib)
Copier après la connexion

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

def decorator(func):
    return func()
 
 
@decorator
def demo():
    return "function demo return string : Demo"
 
print(demo)
Copier après la connexion

执行上面的程序结果为:

function demo return string : Demo

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:yisu.com
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal