Translate

jeudi 25 janvier 2018

[ Python ] - Les décorateurs

Un décorateur est une fonction qui modifie une fonction (ou une classe) qu’il prend en argument. Il peut même décider s’il exécute la fonction ou pas. Un décorateur peut se placer de deux manières sur une fonction (ou une classe). Soit vous écrivez :

votre_fonction = votre_decorateur(votre_fonction)

Remplacez votre_fonction par le nom de votre fonction et votre_decorateur par le nom du décorateur. Ou alors :

@votre_decorateur def votre_fonction() :
‘’’ Code de la fonction ici’’’

Les décorateurs déjà présents dans Python

Voyons maintenant quelques décorateurs présents dans Python.

@staticmethod
Le décorateur staticmethod permet de rendre une méthode de classe statique, il vous suffit de mettre @staticmethod au dessus de la définition de votre méthode et vous n’aurez plus besoin de créer une instance de votre classe pour apeller la méthode. Voici un exemple :
class MaClass :
@staticmethod def Start() :
       print(« Start »)
Vous n’aurez plus besoin de faire :

MonObject = MaClass() MonObjet.Start() Mais :
MaClasse.Start()




@property

Le décorateur property permet de définir les getters et setters d’une classe. Prenons pour exemple la classe suivante :

class MaClass :
    def __init__(self) :

        self._x = None

Pour définir les getters et setters de _x avec le décorateur @property nous écrirons ceci :

class MaClass :
    def __init__(self) :
        self._x = None

    @property 
    def x(self) :
        return self._x

    @x.setter
    def x(self, value) :
        self._x = value



En écrivant @property nous définissons son getter se qui nous permet de définir le setter avec la propriété @.+ nom du getter + . (point) + mot clé setter

@lru_cache

Avant de vous expliquer le fonctionement du décorateur lru_cache laissez-moi vous montrer d’abord pourquoi nous devons l’utiliser.

La fonction suivante récupère les numéros de la suite de fibonacci. Les numéros de la suite de fibonacci se trouvent en commençant la suite avec 1, 1 et ensuite pour avoir les suivants il faut additionner les deux précédents. Exemple :

1, 1 (le suivant sera 1+1) donc 2. Ce qui nous donne : 1, 1, 2 (le suivant sera 1+2) donc 3. La suite devient : 1, 1, 2, 3. Puis ensuite :
1, 1, 2, 3, 5

1, 1, 2, 3, 5, 8
1, 1, 2, 3, 5, 8, 13,
1, 1, 2, 3, 5, 8, 13, 21
1, 1, 2, 3, 5, 8, 13, 21, 34
1, 1, 2, 3, 5, 8, 13, 21, 34, 55
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233 
1,1,2,3,5,8,13,21,34,55,89,144,233,377 etc...


Ce sera plus simple de le calculer avec la fonction suivante :

def fibonacci(n) : 
    if n == 1 :
        return 1 
    elif n == 2 :
        return 1 
    elif n > 2 :
        return fibonacci(n-1) + fibonacci(n-2)


Ensuite pour la lancer nous faisons une boucle comme ceci :

for n in range(1,101) :
    print(n, ‘ :’, fibonacci(n))

Nous obtenons les 100 premiers numéros de la suite, mais comme vous le voyez, c’est extrèmement long. Ce qui est normal vu qu’il y a récursivité de la fonction.

La récursivité est quand une fonction s’apelle elle-même et ici nous avons même une fonction qui s’apelle deux fois à chaque tour de boucle.




C’est pourquoi nous allons utiliser le décorateur @lru_cache qui va nous permettre de garder en mémoire les résultats précédents de la fonction fibonacci. En fait nous n’avons même pas besoin de savoir comment fonctionne ce décorateur puisqu’il fera le travail pour nous.

Pour cela nous devons l’importer depuis le module functools comme ceci :

from functools import lru_cache

Ensuite il ne nous reste plus qu’à mettre le décorateur au dessus de notre fonction fibonacci en lui donnant l’argument maxsize et en lui mettant comme valeur le nombre d’itérations qu’il doit garder en cache comme ceci :

@lru_cache(maxsize=10000) 
def fibonacci(n) :
    if n == 1 : 
        return 1

    elif n == 2 : 
        return 1

    elif n > 2 :
        return fibonacci(n-1) + fibonacci(n-2)


C’est tout. Relancez le code. Comme vous le voyez, ça va beaucoup plus vite.




Créer un décorateur

Pour créer un décorateur c’est très simple il suffit de déclarer une fonction à l’intérieur de la fonction de votre décorateur. Je vous montre ça avec un exemple :



















Voici le décorateur sur la capture précédente, qui prend en argument la fonction qu’il décore (que je nomme ici : maFonc). J’ai mis quelques prints à l’intérieur pour que vous compreniez le fonctionement. Ensuite à la ligne 59 il y a la fonction intérieure.

Celle qui exécutera du code sur la fonction passée en argument. C’est à l’intérieur de celle-ci que je renvoie ma fonction initiale (celle passée en argument, ou celle qui sera décorée, appelez-là comme vous voulez). Je lui ajoute +1



pour montrer le résultat de cet exemple. (Voir plus bas). Et enfin, je renvoie la fonction intérieure (sans les parenthèses).

Maintenant, la fonction :






Une simple fonction qui renvoie 2 avec le décorateur assigné au dessus de sa définition.
Voyons le résultat (Les captures suivantes) :





D’abord, nous l’apellons et affichons son retour dans un print. Puis :  







Vous voyez que le premier print du décorateur est d’abord apellé, puis ensuite, celui de la ligne 62 et comme en dessous nous retournons la fonction intérieure, c’est à ce moment-là que son print sera affiché, ensuite nous retournons notre fonction +1, donc elle est appellée et son print est affiché, puis sa valeur est retournée dans le print ou la fonction est utilisée.

Plutôt déroutant au début non ? Je vais vous montrer quelque chose de plus surprenant encore. Retirez le print qui appelle la fonction, d’ailleurs ne l’appellez plus du tout et relancez le code. Vous allez me dire que si l’on exécute rien, aucun retour ne sera affiché dans la console. Vous croyez ? Voici ce qui se passe avec le code suivant :






La vous vous dîtes que s’il n’y a que cela, rien ne sera exécuté donc rien ne s’affichera. Et pourtant quand on lance le code :







C’est logique, je vous avais dit au début que votre décorateur pouvait s’écrire aussi sous la forme :


Voici le code :


votre_fonction = votre_decorateur(votre_fonction)

Il est donc normal que le code qui se trouve dans le décorateur soit exécuté.

Les fonctions avec paramètres

Passons à la gestion des paramètres de la fonction initiale. Nous allons créer un décorateur qui va doubler les valeurs des arguments passés à la fonction.

Je sais une fonction peut le faire, mais imaginez que vous aiyez plusieurs fonctions qui fassent des calculs et que pour une raison quelconque vous voudriez doubler toutes les valeurs de toutes les fonctions (ou autre chose, comme fixer une valeur MAX etc...).

Bien au lieu de recoder toutes les fonctions vous n’avez qu’à leur apposer le décorateur au dessus de leur définition et le tour est joué.

Voici le code :




Il suffit de passer le paramètre *args pour spécifier que l’on a un nombre indéfini d’arguments à envoyer à notre fonction ligne 68 et on retourne args dans la fonction ligne 70.

Pour le décorateur c’est dans la fonction intérieure qu’il faut passer le paramètre *args (ligne 59) ensuite on fait les traitements que l’on veut dessus et on le passe en argument de notre fonction (ligne 62). 

A la ligne 73 je fais un essai en envoyant quelques nombres à ma fonction et j’obtiens le résultat de la capture suivante :
  





Comme vous le voyez, les résultats sont doublés.

J’ai passé le mot-clé *args pour un nombre indéfinis d’arguments mais j’aurais pu passer **kwargs pour les arguments nommés ou même des arguments quelconques.

En ce qui concerne l’intérêt du décorateur de mon exemple, gardez en tête que vous créerez des décorateurs beaucoup plus utiles, comme le lru_cache du début du chapitre.


Cet article est tiré de mon livre qui est en vente sur Amazon




Aucun commentaire:

Enregistrer un commentaire