Python : pack et unpack, emballer et déballer

Une fonctionnalité très appréciable en Python est la possibilité de faire du parking et de l’unpacking… Soit en français de l’emballage et du déballage. Ceci dit, je ne sais pas à quel point les termes français sont compris, j’utiliserai donc les anglicismes.

Vous faites probablement déjà de l’unpacking en Python car il y a une structure assez naturelle pour récupérer les éléments d’une séquence. Et aussi d’autres de manières plus discrète comme l’inversion du contenu de deux variables. Peut-être ne vous en êtes pas rendu compte.

Dans cet article, nous allons faire le tour de ce que propose le pack/unpack.

Unpacking automatique d’une séquence

Lorsque vous affectez une séquence (liste, tuple…) à une variable, vous… affectez cette séquence à la variable… Merci capitaine Obvious.

Mais vous savez peut-être déjà que vous pouvez affecter cette séquence à un nombre de variables égal au nombre d’éléments de la séquence.

name, job = ("Arthur", "King")
print(name)
print(job)

Vous avez donc ici affecté en une ligne la valeur « Arthur » à la variable name et « King » à la variable job. C’est une fonctionnalité très pratique en Python utilisable à chaque fois que vous avez une affectation de séquence.

total_minutes = 75
hours, minutes = divmod(total_minutes, 60)

for index, value in get_sequence():
    ...

En passant, si vous combinez cette fonctionnalité avec la déclaration de n-uplets (tuples), vous avez de quoi démystifier l’instruction d’inversion de contenu de variables en Python :

a = 10
b = 20

a, b = b, a

Vous comprenez que ligne 4, à la droite de l’expression vous créez un n-uplet que vous déballez immédiatement pour affecter ses valeurs à deux variables. Le fait que ce soit les mêmes permet de les inverser.

Déballer une séquence avec le star operator

Dans certains cas (souvent en fait, nous y reviendrons), vous avez besoin de déballer une séquence pour utiliser les valeurs de celle-ci. Prenons l’exemple de l’affichage de durée sous forme d’heure qui pourrait se faire de la manière suivante :

print("Il reste {}h{:02}".format(hours, minutes)

Mais, vous avez le temps en un total de minutes. Ce que vous voulez passer en argument de .format() est alors le retour de divmod() ou plutôt les éléments de la séquence retournée par divmod().

Vous ne pouvez pas passer ce retour en argument car il s’agit d’une donnée (un tuple) et c’est deux entiers qui sont attendus. Vous avez donc besoin de forcer l’unpacking.

Ceci est évidemment possible grâce un opérateur : l’opérateur splat ou plus communément appelé le star-operator. Il s’agit du symbol étoile (*) qui doit être juste avant la valeur (ici le retour de la fonction) à déballer :

total_minutes = 75

print("Il reste {}h{:02}".format(*divmod(total_minutes, 60)))

Emballage (packing) des données

Dans certains cas, vous aurez besoin de faire l’opération inverse, c’est à dire emballer des données sous forme d’une séquence. Prenons un cas pratique : vous avez les habitants d’un royaume sous forme d’une liste. Le premier élément est le roi, le second le mage et les suivants les chevaliers. Le nombre de ces derniers est inconnu (variable en fonction des royaumes). Vous voulez récupérer ces données dans des variables king, wizard et knights. Bien évidemment le déballage automatique ne marche pas puisqu’il faut autant de variables que de données et chaque variable ne référencera qu’une donnée.

Et bien là aussi nous utiliserons l’opérateur splat… mais de l’autre coté :

kingdom = ["Arthur", "Merlin", "Lancelot", "Robin", "Perceval", "Karadoc"]

king, wizard, *knights = kingdom
print(knights)

Et oui, l’opérateur pour faire du pack ou du unpack est le même, ce qu’il fera réellement dépends donc de l’endroit où il est utilisé.

Les fonctions variadiques

Ceci nous conduit à une syntaxe que vous avez probablement déjà dû voir, des fonctions déclarées avec les paramètres suivants :

def func(*args):
    pass

def func(**kwargs):
    pass
def func(*args, **kwargs):
    pass

Ces fonctions sont des fonctions variadiques. Les paramètres args et kwargs (dont le nom est une convention, c’est la simple ou double étoile qui compte) deviennent dans la fonction, respectivement, un N-uplat et un dictionnaire. Le premier est destiné à recevoir les arguments positionnés, le second les arguments nommés. Si on combine les deux, voici ce que vous pouvez avoir :

>>> def func(*args, **kargs):
...     print(args, kargs)
... 

>>> func()
() {}

>>> func(1)
(1,) {}

>>> func(1, 2, 3)
(1, 2, 3) {}

>>> func(first=1)
() {'first': 1}

>>> func(first=1, second=2, third=3)
() {'first': 1, 'second': 2, 'third': 3}

>>> func(1, second=2, third=3)
(1,) {'second': 2, 'third': 3}

Bien entendu, vous pouvez combiner ces deux paramètres avec des paramètres conventionnels.

Attention à l’usage que vous faites de ce type de paramètre. Ils ne sont pas une facilité sur les paramètres d’une fonction entre autres parce qu’il sera nécessaire de déterminer où est passée une valeur. Ces fonctions sont intéressantes dans le cas d’interfaces entre d’autres implémentations. On a le cas pratique en Python avec les décorateurs. Cependant, dans ce cas, il faudra aussi déballer le dictionnaire.

Déballage de dictionnaires

L’unpacking fonctionne aussi avec les dictionnaires avec comme différence qu’il faudra utiliser un double étoiles comme pour le variadique. Un cas pratique est de passer des données en tant qu’argument de fonction sous forme d’argument nommés. Voici donc la manière de faire :

>>> def func(a=1, b=2, c=3):
...     print(a, b, c)
... 

>>> value = {"b":20, "c":30}

>>> func(**value)
1 20 30

Cas pratique : flux de fonctions

Un usage pratique du pack et unpack, qu’il soit automatique ou non, est de travailler en flux avec vos fonctions. Reprenons un exemple sur la suite de ce que nous avons vu plus haut.

Soit une durée en minutes de 35 minutes

Soit une heure de début sous forme de chaine de caractères : « 12h42 »

Je souhaite afficher l’heure de fin d’une action ayant la durée précédente, action débutant à l’heure de début, et en utilisant un template de la forme « Action terminée à {}h{:02} ».

On peut commencer par déclarer :

event_duration = 35

start_hour = "12h42"

template = "Action terminée à {}h{:02}"

Essayez d’écrire le code qui permet cet affichage avant de regarder la suite.

Voici comment vous pouvez réaliser ceci en

  • utilisant une fonction calculant le nombre de minutes à partir d’heure et minutes tout en convertissant ces données en entier (utile dans le cas où elle arrivent sous forme de chaine de caractères)
  • utilisant les fonctions standard pour extraire les éléments d’une chaine de caractères
  • utilisant la fonction divmod()
def to_minutes(hours, minutes):
    return int(hours) * 60 + int(minutes)

end_time = to_minutes(*start_hour.split('h')) + event_duration

print(template.format(*divmod(end_time, 60)))

Les grands fous ne passeront même pas par la variable end_time mais je pense que ça nuit à la lisibilité.

En conclusion

Le gain du pack et unpack est réellement de travailler en flux : la sortie d’une fonction peut être utilisée en entrée d’une autre fonction. Si vous concevez correctement vos extracteurs (de csv, bases de données), les données retournée peuvent également être directement déballées dans des constructeurs. Ceci permet d’avoir un code plus concis et adapté à l’évolution.

Si vous avez aimé ce post, n’hésitez pas à laisser un commentaire ci-dessous ou sur la page Facebook 😉

À propos de... Darko Stankovski

iT guy, photographe et papa 3.0, je vous fais partager mon expérience et découvertes dans ces domaines. Vous pouvez me suivre sur les liens ci-dessous.

Vous aimerez aussi...

Laisser un commentaire

En naviguant sur Dad 3.0, vous acceptez l’utilisation de cookies pour une navigation optimale et nous permettre de réaliser des statistiques de visites. Plus d'informations

Le blog Dad 3.0 utilise les cookies pour vous permettre une navigation optimale et nous permettre de réaliser des statistiques de visite. Dad 3.0 affichant des publicités, celles-si utilisent également des cookies pour un ciblage publicitaire. En continuant la navigation sur Dad 3.0, vous acceptez le dépôt et la lecture de cookies.

Fermer