Les Crash-Devs d'un Passionné

Quelles usages pour les décorateurs

/Catégorie/python

Temps de lecture : 2 minutes

Aujourd'hui nous allons voir les décorateurs par le code. Un peu de théorie avant.

Un décorateur est un mécanisme qui prend en paramètre une fonction et retourne une fonction. Le décorateur s'exécute au moment de la définition de la méthode et non lors de l'appel.

Par une simple annotation au-dessus de la méthode, nous mettons en place un décorateur

clipboard
Copier le code
1
2
3
@deco
def my_method():
  ...

En faite python fait cela deco(my_method)

Voici un cas simple :

clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
def decorate(func):
    print("Function parameter {0}".format(func))
    return func

class Test:

    @decorate
    def get_text(self):
        print("Call get_text method !")

custom = Test()
custom.get_text()

# Function parameter <function get_text at 0x0000000004C1DDD8>
# Call get_text method !

Il est aussi possible de faire un décorateur avec une méthode à l'intérieur, ce que l'on appelle une closure
En passant les paramètres dynamiques (*args et **kwargs) cela permet de décorer une méthode qui possède des paramètres.

clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
"""
Test decorator with sub method
"""

def decorate(func):
    def func_custom(*args, **kwargs):
        print("Function parameter {0}".format(func))
        return func(*args, **kwargs)

    return func_custom


class Test:

    @decorate
    def get_text(self, content):
        print(f"Call get_text method with {content=}!")


custom = Test()
custom.get_text("Hello")

# Function parameter <function get_text at 0x00000000047DDDD8>
# Call get_text method with content='Hello'!

On peut aussi faire un décorateur sous forme de classe. La subtilité est d'utiliser la métaclasse __call__ pour exécuter la méthode.

clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
"""
Test decorator with class
"""

class Decorate:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print("Call decorator class")
        return self.func(self, *args, **kwargs)

class Test:

    @Decorate
    def get_text(self):
        print("Call get_text method !")


custom = Test()
custom.get_text()

# Call decorator class
# Call get_text method !

On peut envisager l'usage des multiples décorations. L'ordre est important car le 2ᵉ décorateur est injecté dans le 1ᵉʳ décorateur qui lui-même prend la méthode décorée

clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
"""
Test decorator with double decorator
"""

def decorate(func):
    def func_custom(*args, **kwargs):
        print("Function parameter {0}".format(func))
        return func(*args, **kwargs)

    return func_custom

def wrap(func):
    def func_custom(*args, **kwargs):
        print("*********************")
        func(*args, **kwargs)
        print("*********************")

    return func_custom

class Test:
    @decorate
    @wrap
    def get_text(self):
        print("Call get_text method !")


custom = Test()
custom.get_text()

# Function parameter <function func_custom at 0x000000000465DEB8>
# *********************
# Call get_text method !
# *********************

Voilà la théorie exposée, maintenant à quoi cela sert-il ?

  • Faire un wrapper
clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
"""
Test decorator with class
"""

class Decorate:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print("Call decorator class")
        return self.func(self, *args, **kwargs)

class Test:

    @Decorate
    def get_text(self):
        print("Call get_text method !")


custom = Test()
custom.get_text()

# Call decorator class
# Call get_text method !
  • Mettre des logs
clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
"""
Test decorator with log decorator
"""

def decorate(text):
    def func_custom(func):
        print(text)
        return func

    return func_custom

class Test:

    @decorate("Call get_text method")
    def get_text(self, *args, **kwargs):
        print("Inside method...")


custom = Test()
custom.get_text()

# Call get_text method
# Inside method...
  • Déprécier une methode
clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
"""
Test decorator with deprecated decorator
"""

def deprecated(func):
    def func_custom(*args, **kwargs):
        print("The method {0} is deprecated".format(func.__name__))
        return func(*args, **kwargs)

    return func_custom

class Test:

    @deprecated
    def get_text(self, *args, **kwargs):
        print("Inside method...")


custom = Test()
custom.get_text()

# The method get_text is deprecated
# Inside method...
  • Gérer les permisssions, validation des données ...

Si vous avez d'autres cas d'usages n'hésitez pas à les partager en commentaires ;-)