Args, Kwargs et décorateurs sous Python

Le passage de paramètres multiples avec ou sans clé et son application aux décorateurs de fonctions

Le passage de paramètres multiples via les mots clés args et kwargs est en général une fonctionnalité peu utilisée par les développeurs qui débutent. Il est vrai que le code s'en trouve plus difficile à lire mais les avantages sont néanmoins nombreux et il serait par conséquent dommage de s'en priver.
Le principe est simple : plutôt que de définir au préalable dans la signature de la fonction l'ensemble des arguments que celle-ci va attendre, nous lui passons une liste de longueur indéterminée à laquelle elle accèdera via les indices.

Un premier exemple

Partons d'une première fonction très basique, dans laquelle nous demandons l'affichage du seul argument passé en paramètre. Jusqu'ici rien de nouveau :


def maFonction(x):
  print(x)
  
maFonction(22)
22

Ajoutons a présent le mot clé *args afin de passer à notre fonction une suite de paramètres et demandons lui de les afficher tous. Nous utilisons ici une boucle mais notez que nous aurions pu accéder à nos arguments via une instruction de la forme args[0]


def maFonction(x, *args):
  print(x)
  if args:
    for i in args:
      print('Args : ',i)
      
maFonction(22, 2, 3, 4)
22
Args :  2
Args :  3
Args :  4

Enfin nous ajoutons à présent le mot clé *kwargs à notre fonction. *Kwargs est l'équivalent de *args à l'exception près qu'il va gérer les clés (kw signifiant keyword), comme nous le voyons ci-dessous :


def maFonction(x, *args, **kwargs):
  print(x)
  if args:
    for i in args:
      print('Args : ',i)
  if kwargs:
    for key, val in kwargs.items():
      print('Kwargs : ',key,val,sep=' > ')
      
maFonction(22, 2, 3, 4, option=12, unecle='une valeur')
22
Args :  2
Args :  3
Args :  4
Kwargs :  > option > 12
Kwargs :  > unecle > une valeur

Comme vous pouvez le constater tout ceci reste très simple d'utilisation, mais pourtant très puissant quant aux possibilités que cela peut offrir au développeur.
Nous avons évoqué précédemment le fait d'accéder aux arguments via leur indice. Il faudra veiller dans ce cas de figure à traiter l'exception pouvant être levée sur un indice hors plage, ce que nous allons voir de suite ...

Index out of range !

Dès lors que l'on accède aux éléments d'une liste ou d'un tableau via ses indices, on est susceptible de lever une exception de type out of range. Il convient de la gérer pour éviter que le programme ne plante.
Voici un exemple d'exception levée :


def maFonction(*args):
  if args:
    print(args[3])
      
maFonction(3,4,5)
------------------------------------
IndexError          Traceback 
 
in ()
----> 1 maFonction(3,4,5)

in maFonction(*args)
      2 def maFonction(*args):
      3   if args:
----> 4     print(args[3])

IndexError: tuple index out of range

Et la version dans laquelle l'exception a été gérée :


def maFonction(*args):
  if args:
    try :
      print(args[3])
    except IndexError as e:
      print("Indice n'existe pas !")

maFonction(3,4,5)
Indice n'existe pas !

Les décorateurs

Les décorateurs mériteraient à eux seuls un article dédié, et cela sera très certainement fait dans un avenir proche. Pour autant, nous ne pourrions évoquer les extensions args et kwargs sans parler des apports qu'elles représentent lorsqu'elles sont associées aux décorateurs.
Pour ceux qui ne connaissent pas encore ou qui désirent un rafraichissement, commençons par rappeler ce que sont les décorateurs.

Comme vous le savez très certainement, il est possible en python de passer en paramètre d'une fonction une autre fonction afin que celle-ci soit appelée par la première, comme ceci :


def bienvenue():
  return 'Bienvenue !'

def bienvenue_abientot(mafonction):
  print(mafonction(),'A bientot !', sep='\n')

bienvenue_abientot(bienvenue)
Bienvenue !
A bientot !

Nous pouvons également obtenir le même résultat en nous évitant l'appel explicite de mafonction dans la fonction finale bienvenue_abientot, comme ceci :


def decorateur(fonction):
  def bienvenue():
    print('Bienvenue !')
    fonction()
  return bienvenue

def fct_decoree():
  print('A bientot !')

fct_decoree = decorateur(fct_decoree)
fct_decoree()
Bienvenue !
A bientot !

Comme vous le voyez, nous avons renommé notre fonction bienvenue_abientot en fct_decoree. Par ailleurs, le code exécuté par fct_decoree est beaucoup plus simple puisqu'il n'y a plus d'appel à la fonction bienvenue. Cette dernière a en effet été encapsulée pour devenir un décorateur. Nous pouvons par conséquent d'ores et déjà définir un décorateur comme étant une surcouche qui va venir apporter une fonctionnalité supplémentaire à une fonction sans qu'il y ait besoin de modifier le code de celle-ci.
Nous avons appelé notre décorateur "decorateur" mais nous aurions pu lui donner un autre nom. Une fonction décorée par notre décorateur peut l'être de façon encore plus simple que ce que nous avons fait plus haut, en utilisant l'arobase, comme ceci :


@decorateur
def fct_decoree_bis():
  print('Comment allez-vous ?')

fct_decoree_bis()
Bienvenue !
Comment allez-vous ?

Nous avons ci-dessus implémenté une nouvelle fonction fct_decoree_bis également décorée par notre décorateur. Cette fois nous nous contentons de le signifier en déclarant le nom de notre décorateur précédé d'une arobase avant la signature.

Maintenant que nous avons fait ce rappel sur les décorateurs, revenons-en aux instructions args et kwargs. Couplées aux décorateurs, ces instructions vont s'avérer être un formidable outil. Args et kwargs vont en effet nous permettre de généraliser l’utilisation des décorateurs.

En guise d'exemple, imaginons le cas suivant : vous implémentez plusieurs fonctions qui vont éditer des données sensibles à l'écran. Pour chacune d'entre elles vous voulez vous assurer que le profil de l'utilisateur est admin. Vous avez bien sur la possibilité de le faire dans toutes vos fonctions, mais avouez qu'il serait mieux de centraliser ce contrôle pour faciliter toute maintenance future du code. Pour arriver à nos fins, les décorateurs vont nous y aider. Les fonctions décorées étant bien sur très différentes les unes des autres, elles n'auront pas les mêmes paramètres, c'est là qu'interviennent les instructions args et kwargs.


def verification_profil(fonction):
  def verif(*args):
    if args[0]=='admin':
      fonction(*args)
    else:
      print("Vous n'etes pas un admin !")
  return verif

@verification_profil
def afficher_parametres_connexion(profile, nombase, motpasse):
  print("Les parametres sont ", nombase, motpasse)

@verification_profil
def afficher_adresse_serveur(profile, adresse):
  print("L'adresse du serveur est ", adresse)

profile = 'admin'
nombase = 'mabase'
motpasse = 'azerty'
adresse = '123.123.123.123'        
afficher_parametres_connexion(profile, nombase, motpasse)
afficher_adresse_serveur(profile, adresse)
Les parametres sont  mabase azerty
L'adresse du serveur est  123.123.123.123

Nous avons défini un décorateur unique verification_profil qui va venir ajouter à 2 fonctions une couche supplémentaire de contrôle bien que, d'une part, ces 2 fonctions aient un nombre d'arguments diffèrent et, d'autre part, que le code de ces dernières n'ait pas été modifié.
Notre seul prérequis a consisté à passer systématiquement le profil de l'utilisateur en premier argument de chacune des fonctions décorées.


Retrouvez dans la rubrique "Nos datasets" toutes les données dont vous aurez besoin pour tester et pratiquer !