Les Crash-Devs d'un Passionné

*args et **kwargs en Python : les paramètres nommées et positionnels sur des fonctions

/Catégorie/python

Temps de lecture : 3 minutes

Une fonction en python aura cette forme ci

clipboard
Copier le code
1
2
def add(a, b):
   return a + b

Si on part de cette fonction tout simple d'addition, on se rend vite compte de ses limites. Elle ne peut pas prendre plusieurs paramètres. Pour faire cela on est obligé de modifier la signature de la fonction

clipboard
Copier le code
1
2
def add(a, b, c):
   return a + b + c

C'est une solution peu dynamique.

Nous allons donc voir comment faire cela tout en ayant un code python souple

Les paramètres positionnels : *args

On les trouve souvent sous *args dans la littérature même si ce n'est qu'une convention. On peut leur donner le nom que vous préférez (même si je vous conseille fortement de respecter la convention), il faut juste conserver le *

Ce caractère n'est pas un pointeur, c'est une déstructuration de liste que l'on nomme unpack

clipboard
Copier le code
1
2
3
4
5
>>> a = [1, 2, 3]
>>> print(*a)
1 2 3
>>> print(a)
[1, 2, 3]

Dans cet exemple il prend les valeurs de cette liste pour les passer en paramètre de la fonction print()

D'ailleurs si on regarde la doc de cette fonction on se rend compte qu'elle est compatible avec l'unpacking de liste

clipboard
Copier le code
1
2
3
4
5
>>> help(print)
Help on built-in function print in module builtins:

print(*args, sep=' ', end='\n', file=None, flush=False)
...

On va appliquer ce concept sur notre fonction d'addition

clipboard
Copier le code
1
2
3
4
5
6
7
>>> def add(*args):
...     print(args)
...     
>>> add(1, 2)
(1, 2)
>>> add(1, 2, 3)
(1, 2, 3)

On se rend compte que notre fonction s'adapte à ces paramètres sans se crasher (je n'ai pas implémenté la logique d'addition mais vous avez compris le concept)

Les paramètres nommés : **kwargs

On voit également **kwargs dans la littérature même si ce n'est qu'une convention. On peut leur donner le nom que vous préférez (même si je vous conseille fortement de respecter la convention), il faut juste conserver le **

C'est une déstructuration de dictionnaire que l'on nomme unpack

On garde le même principe que sur les paramètres positionnels mais cette fois sur notre fonction on doit nommer le nom du paramètre

clipboard
Copier le code
1
2
3
4
5
6
7
>>> def add(**kwargs):
...     print(kwargs)
...     
>>> add(a=1, b=2)
{'a': 1, 'b': 2}
>>> add(a=1, b=2, c=3)
{'a': 1, 'b': 2, 'c': 3}

Et les deux

On peut évidemment combiner les 2 types de paramètres, mais on doit impérativement les mettre dans un ordre précis. Les paramètres positionnels et ensuite les paramètres nommés

clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
>>> def add(*args, **kwargs):
...     print(args, kwargs)
...     
>>> add(0, a=1, b=2)
(0,) {'a': 1, 'b': 2}
>>> add(a=0, 1)
  File "<python-input-12>", line 1
    add(a=0, 1)
              ^
SyntaxError: positional argument follows keyword argument

C'est une solution très souple lorsqu'on ne connaît pas à l'avance les paramètres de la fonction.

Quand l'utiliser ?

En cas d'absolue nécessité, car cela alourdit le code. On ne peut pas, au 1er coup d'œil, voir ce qu'attend la fonction : on doit aller regarder son contenu et lire attentivement la documentation.

D'ailleurs si vous l'utilisez mettez une docstring avec des exemples

J'utilise très souvent l'unpacking de liste sur des fonctions helpers pour faire de la construction de chaîne de caractères ou indirectement comme cela

clipboard
Copier le code
1
logger.info("My message %s %s", (a, b))

Ou encore du unpacking de dictionnaire pour injecter mes données dans un objet

pydantic

clipboard
Copier le code
1
2
data = {"name": "David", "zip", "63"}
MyPydanticObject(**data)