unpacking – Sam & Max http://sametmax.com Du code, du cul Wed, 30 Oct 2019 15:34:04 +0000 en-US hourly 1 https://wordpress.org/?v=4.9.7 32490438 Enrichissement du tuto sur l’opérateur splat http://sametmax.com/enrichissement-du-tuto-sur-loperateur-splat/ http://sametmax.com/enrichissement-du-tuto-sur-loperateur-splat/#comments Tue, 01 Sep 2015 14:13:37 +0000 http://sametmax.com/?p=16864 Ça, c’est un super tuto que vous ne trouverez pas ailleurs. Et maintenant il est encore plus super, car je lui ai ajouté l’unpacking étendu de Python 3, et l’unpacking généralisé de Python 3.5. Et en prime les modifications cosmétiques et pratiques habituelles.

Si vous ne l’avez pas lu, sautez dessus, il est formidable !

En parlant de sauter, ne sautons pas un repas :

Et oui ! Le foot fetish est une niche très porteuse.

]]>
http://sametmax.com/enrichissement-du-tuto-sur-loperateur-splat/feed/ 8 16864
Qu’est-ce que l’unpacking en Python et à quoi ça sert ? http://sametmax.com/quest-ce-que-lunpacking-en-python-et-a-quoi-ca-sert/ http://sametmax.com/quest-ce-que-lunpacking-en-python-et-a-quoi-ca-sert/#comments Fri, 26 Dec 2014 09:03:15 +0000 http://sametmax.com/?p=13019 Ce terme apparaît dans de nombreux articles du blog, et je prends parfois le temps de l’expliquer superficiellement. Évidemment, à de nombreux moments j’ai fait des tutos en ayant la connaissance de l’unpacking comme prérequis, et rien vers quoi faire un lien. Corrigeons ça, en attendant que je traduise les slides sur WAMP.

Le principe de base

Normalement, si vous voulez mettre le contenu d’un tuple dans des variables, vous devez procéder ainsi :

>>> ducks = ('riri', 'fifi', 'loulou')
>>> duck1 = ducks[0]
>>> duck2 = ducks[1]
>>> duck3 = ducks[2]
>>> print(duck1)
'riri'
>>> print(duck2)
'fifi'
>>> print(duck3)
'loulou'

L’unpacking, qu’on pourrait traduire par le terme fort moche de “déballage”, dans le sens “ouvrir un colis”, permet de faire la même chose, bien plus facilement :

>>> duck1, duck2, duck3 =  ducks
>>> print(duck1)
'riri'
>>> print(duck2)
'fifi'
>>> print(duck3)
'loulou'

Il n’y a rien à faire, c’est automatique. La seule condition est que le nombre de variables à gauche du signe égal soit le même que le nombre d’éléments dans la collection de droite.

D’ailleurs, ça marche même avec un seul élément :

>>> ducks = ('riri',)
>>> duck1, = ducks # notez la virgule
>>> duck1
'riri'

Et ça marche avec n’importe quel itérable, pas uniquement les tuples. Avec une liste, une string, un générateur…

>>> a, b, c, d = [1, 2, 3, 4]
>>> c
3
>>> a, b = "12"
>>> b
'2'
>>> def yolo():
    yield "leroy"
    yield "jenkins"
...
>>> nom, prenom = yolo()
>>> nom
'leroy'
>>> prenom
'jenkins'

Ça marche bien entendu avec un dico ou un set, mais comme ils ne sont pas ordonnés, c’est pas très utile.

Astuces autour de l’unpacking

On peut utiliser l’unpacking dans des endroits inattendus. Par exemple, pour échanger la valeur de deux variables :

>>> a = 1
>>> b = 2
>>> a, b = (b, a)
>>> a
2
>>> a, b = b, a # les parenthèses sont facultatives dans les tuples
>>> b
2

Puisqu’on est dans les tuples sans parenthèses, on peut retourner un tuple et donner l’illusion de retourner plusieurs variables :

>>> def duckmebaby():
...     return "rifi", 'filou', 'louri'
...
>>> et, hop, la = duckmebaby()
>>> et
'rifi'
>>> hop
'filou'
>>> la
'louri'

Allons plus loin.

On peut utiliser l’unpacking à l’intérieur d’une boucle for. Souvenez vous que les itérables peuvent contenir d’autres itérables. Par exemple, j’ai une liste qui contient 3 tuples, chaque tuple contient deux éléments :

>>> scores = [('Monique', '3'), ('David', 10), ('Dick', 1)]
>>> for score in scores:
...     print(score)
...
('Monique', '3')
('David', 10)
('Dick', 1)

Si je veux afficher le nom et le score l’un en dessous de l’autre :

>>> for nom_et_score in scores:
...     print(nom_et_score[0])
...     print(nom_et_score[1])
...
Monique
3
David
10
Dick
1

Je peux appliquer l’unpacking dans la boucle pour rendre cette opération plus élégante :

>>> for nom, score in scores:
...     print(nom)
...     print(score)
...
Monique
3
David
10
Dick
1

Cela marche avec des itérables plus gros, bien entendu. C’est aussi particulièrement utile avec des dictionnaires car on peut les transformer en itérable de tuples :

>>> scores = {'Monique': '3', 'David': 10, 'Dick': 1}
>>> scores['Monique']
'3'
>>> scores.items() # transformation !
dict_items([('Monique', '3'), ('David', 10), ('Dick', 1)])
>>> for nom, score in scores.items():
...     print(nom)
...     print(score)
...
Monique
3
David
10
Dick
1

Tout aussi utile, mais plus compliqué, est l’usage de l’unpacking dans l’appel de fonction. Pour cela, on utilise l’opérateur splat, l’étoile en Python.

Soit une fonction qui additionne des nombres :

>> def add(a, b, c):
...     return a + b + c
...
>>> add(1, 2, 3)
6

Oui, imaginons que je suis complètement débile, et que j’ai cette fonction pérave dans mon code. Vous noterez dans les articles que je l’utilise souvent sur le blog. C’est la fonction fourre tout pour expliquer un truc quand j’ai pas d’idée.

Maintenant, imaginez que je veuille additionner des canards. Si, ça marche en Python :

>>> 'riri' + 'fifi' + 'loulou' # what the duck ?
'rirififiloulou'

Maintenant je me refais mon tuples de canards :

>>> # nous entrerons dans la bande à picsou, youhou
>>> duckyou = ('riri', 'fifi', 'loulou')

Si je veux utiliser ma fonction pourrie pour mon use case stupide, je ferai ceci :

>>> add(duckyou[0], duckyou[1], duckyou[2])
'rirififiloulou'

Voilà une perte de productivité intolérable, c’est pas comme ça qu’on va faire fructifier son sou fétiche.

On peut forcer l’unpacking avec l’étoile :

>>> add(*duckyou)
'rirififiloulou'

Si on oublie l’étoile, le premier paramètre reçoit tout le tuple, et les autres paramètres rien :

>>> add(duckyou)
Traceback (most recent call last):
  File "", line 1, in 
    add(1)
TypeError: add() missing 2 required positional arguments: 'b' and 'c'

Les fonctions ont même le droit à un bonus car on peut unpacker des dictionnaires en utilisant la double étoile. Ca ne marche qu’avec les fonctions, et ça va déballer le dico pour que chaque paire clé/valeur soit passée comme nom et valeur de l’argument :

>>> def pas_add(arg1, arg2):
    print(arg1)
    print(arg2)
...
>>> pas_add(arg1="Je suis la valeur 1", arg2="Je m'en branle de qui tu es")
Je suis la valeur 1
Je m'en branle de qui tu es
>>> dicocorico = {'arg1': 'cotcot', 'arg2': 'ouai je pête un cable, l\'avion me soule'}
>>> pas_add(**dicocorico)
cotcot
ouai je pête un cable, l'avion me soule

Quand on unpacke des paramètres, il faut s’assurer que le nombre d’arguments passé n’est pas supérieur à ceux existant, sinon ça plante :

>>> dicocorico = {'arg1': 'cocot', 'arg2': 'ouai je pête un cable, l\'avion me soule', 'dang': 'je suis en trop et ça fait chier tout le monde'}
>>> pas_add(**dicocorico)
Traceback (most recent call last):
  File "", line 1, in 
    pas_add(**dicocorico)
TypeError: pas_add() got an unexpected keyword argument 'dang'
>>> stuplet = (1, 2, 3)
>>> pas_add(*stuplet)
Traceback (most recent call last):
  File "", line 1, in 
    pas_add(*stuplet)
TypeError: pas_add() takes 2 positional arguments but 3 were given

Par contre, rien ne vous empêche de fournir moins d’arguments et de remplir les autres à la main :

>>> def encore_add(a, b, c, d):
    return a + b + 0 + c + d # je feinte
...
>>> encore_add(10, *stuplet)
16

Et on peut bien entendu faire le mega mix. Par exemple, prenons la fonction print, dont la signature accepte une infinité d’arguments positionnels et quelques arguments nommés :

print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)

Aller, on va lui unpacker sa mère :

>>> ducks = ['riri', 'fifi', 'loulou'] # is this duck typing ?
>>> keywords = {'sep': ' / ', "end": " : vous êtes du coin ? \n"}
>>> print('picsou', *ducks, **keywords)
picsou / riri / fifi / loulou : vous êtes du coin ?

Ça c’est fait.

Python 3, c’est du chocolat

En Python 3, l’unpacking a été amélioré, et on peut maintenant faire de l’unpacking partiel :

>>> # exemple 100% repompé d'un autre article du blog. Duck it.
>>> l = list(range(5))
>>> l
[0, 1, 2, 3, 4]
>>> a, *b = l
>>> a
0
>>> b
[1, 2, 3, 4]
>>> a, *b, c = l
>>> a
0
>>> b
[1, 2, 3]
>>> c
4

Ce qui peut être très pratique sur les longs itérables. Comment obtenir la dernière ligne d’un fichier ?

>>> *contenu, dernire_ligne = open('/etc/fstab')
>>> dernire_ligne
'UUID=0e8c3132-8fa2-46d5-a541-2890db9b371f none            swap    sw              0       0\n'

Ou alors, dans une boucle :

>>> for initiale, *reste in ducks:
    print(initiale)
...
r
f
l
]]>
http://sametmax.com/quest-ce-que-lunpacking-en-python-et-a-quoi-ca-sert/feed/ 9 13019
5 choses à apprendre en priorité en Python http://sametmax.com/5-choses-a-apprendre-en-priorite-en-python/ http://sametmax.com/5-choses-a-apprendre-en-priorite-en-python/#comments Sun, 22 Dec 2013 08:57:17 +0000 http://sametmax.com/?p=8376 Quand on apprend un nouveau langage de programmation, on apprend d’abord les bases. Et pour la plupart des langages, elles sont communes : déclarer une variable, faire des conditions et des boucles, faire des fonctions, importer un code d’un autre fichier, etc.

Ce qui va différencier le moment où vous savez programmer dans CE langage, ce sont des notions qui lui sont spécifiques et que vous commencez à maitriser.

Voici 5 notions spécifiques au langage qu’il faut apprendre en priorité si vous voulez pouvoir dire “je code en Python” :

Pip

Pip est la moyen le plus utilisé d’installer une bibliothèque externe dans l’environnement Python. Dès qu’on veut faire un projet sérieux, on en a besoin. Tellement qu’il va en fait être inclus par défaut dans Python 3.4.

Lire l’article sur pip.

Virtualenv

Virtualenv permet d’isoler plusieurs installations de Python. A partir du moment où l’on travaille sur plusieurs projets en même temps, il devient vite indispensable. Mais personnellement, je l’utilise même quand je n’ai qu’un projet installé sur une machine car il me permet de le séparer du setup Python du système et d’utiliser des hooks.

Un outil qui a été ajouté dans la lib standard en Python 3.3. J’apprécie que le pragmatisme de l’évolution de Python qui intègre petit à petit les projets qui se sont révélés les outils de facto dans la communauté.

Lire l’article sur virtualenv.

Les listes en intention

J’ai envie de dire l’itération en générale, mais c’est un très vaste sujet, et il est couvert en grande partie par les 3 derniers points.

La liste en intention, ou liste en compréhension, est une manière de boucler sur un itérable (souvent une liste), avec optionellement un filtre, afin de produire une nouvelle liste. En une ligne.

C’est stylistiquement la marque de fabrique de Python (même si c’est piqué à Haskell). C’est également ce qui le rend aussi expressif. On peut presque coder tout un programme en déclaratif avec des enchainements de listes en intention.

C’est beau, propre, efficace et court. IN-DIS-PEN-SA-BLE.

Lire l’article sur les listes en intention.

L’unpacking

L’unpacking est une autre fonctionalité typiquement pythonienne qui permet de prendre un itérable (souvent un tuple), et de mettre ses éléments dans des variables d’une traite.

Cela permet d’augmenter drastiquement la lisibilité des programmes.

Lire les articles sur l’unpacking.

Les générateurs

Les générateurs permettent non seulement un énorme gain en performance, mais en plus ils autorisent le traitement itératif de flux de données dont on ne connait pas la taille en avance, voire de taille infinie. Si vous utilisez des expressions génératrices, vous pourrez le faire en déclaratif. Si vous utilisez yield, vous pourrez cacher un algorithme complet derrière une simple boucle for.

Lire l’article sur yield.

Le reste ?

Tout le reste, c’est du détail. Les décorateurs, la POO, l’opérateur with, les métaclasses, les astuces magiques pour faire ceci ou cela. C’est bien, mais ça peut attendre. Ce sont ces 5 notions, qui, bien utilisées, feront d’un programmeur un dev Python.

]]>
http://sametmax.com/5-choses-a-apprendre-en-priorite-en-python/feed/ 9 8376
Changement dans l’unpacking des iterables en Python 3 http://sametmax.com/changement-dans-lunpacking-des-iterables-en-python-3/ http://sametmax.com/changement-dans-lunpacking-des-iterables-en-python-3/#comments Fri, 20 Dec 2013 07:49:59 +0000 http://sametmax.com/?p=7656 fait le tour de cette fonctionalité merveilleuse, et PAF, on découvre encore autre chose. Par exemple, la syntaxe a été améliorée avec Python 3, et accepte maintenant un unpacking partiel !]]> Ahhh, l’unpacking… On croit qu’on a complètement fait le tour de cette fonctionalité merveilleuse, et PAF, on découvre encore autre chose.

Par exemple, la syntaxe a été améliorée avec Python 3, et accepte maintenant un unpacking partiel !

Ca se fait en l’utilisant l’opérateur splat, c’est à dire l’étoile :

>>> l = list(range(5))
>>> l
[0, 1, 2, 3, 4]
>>> a, *b = l
>>> a
0
>>> b
[1, 2, 3, 4]
>>> a, *b, c = l
>>> a
0
>>> b
[1, 2, 3]
>>> c
4

Ca marche bien entendu également dans les boucles for.

]]>
http://sametmax.com/changement-dans-lunpacking-des-iterables-en-python-3/feed/ 6 7656
Petite astuce d’unpacking en Python http://sametmax.com/petite-astuce-dunpacking-en-python/ http://sametmax.com/petite-astuce-dunpacking-en-python/#comments Sat, 07 Dec 2013 08:46:44 +0000 http://sametmax.com/?p=8245 L’unpacking, fonction géniale de Python s’il en est, peut se faire sur un seul element :

>>> a = [1]
>>> b, = a
>>> b
1

Pour cet exemple, pas super utile. Par contre dans une boucle :

>>> l = ([1], [1], [1])
>>> for i, in l:
...     print(i)
...     
1
1
1
]]>
http://sametmax.com/petite-astuce-dunpacking-en-python/feed/ 2 8245
Paramètres imbriqués dans une fonction Python http://sametmax.com/parametres-imbriques-dans-une-fonction-python/ http://sametmax.com/parametres-imbriques-dans-une-fonction-python/#comments Sat, 23 Nov 2013 07:24:35 +0000 http://sametmax.com/?p=7613 Une fonction très peu connue de Python est la possibilité de définir un paramètre en indiquant qu’il s’agit d’une séquence. Python va automatiquement appliquer l’unpacking dessus :

>>> def message_geolocalise((long, lat), message):
    print '%s' % message
    print 'Longitude : %s' % long
    print 'Latitude : %s' % lat
...     
>>> point = (4.344, 2.44)
>>> message = "glittering prizes"
>>> message_geolocalise(point, message)
glittering prizes
Longitude : 4.344
Latitude : 2.44

Cette fonctionnalité a été retirée en Python 3, jugée rarement utilisée, et surtout facilement remplaçable par un unpacking explicite :

>>> def message_geolocalise(coord, message):
    long, lat = coord
    print '%s' % message
    print 'Longitude : %s' % long
    print 'Latitude : %s' % lat

En effet, en Python on peut utiliser de l’unpacking imbriqué :

a, (b, c) = [1, [2, 3]]

Et cela a été abusé en le mélangeant la syntaxe des paramètres imbriqués, qui rend le truc franchement illisible.

Voilà, c’était le post “vous aviez un poney que vous ne saviez pas et maintenant que vous le savez vous l’avez plus”.

]]>
http://sametmax.com/parametres-imbriques-dans-une-fonction-python/feed/ 5 7613
La virgule n’est pas un opérateur en Python http://sametmax.com/la-virgule-nest-pas-un-operateur-en-python/ http://sametmax.com/la-virgule-nest-pas-un-operateur-en-python/#comments Fri, 22 Feb 2013 10:57:33 +0000 http://sametmax.com/?p=4379 Premiers tutos, et vous apprenez l’existence du tuple :

t = (1, 2)
print t
## (1, 2)
print type(t)
## 

Puis en creusant un peu, vous apprenez qu’un tuple, en fait, c’est juste une série d’objets séparés par des virgules :

t = 1, 2
print t
## (1, 2)

Les parenthèses sont optionnelles pour créer un tuple.

Du coup si on rajoute l’unpacking :

a, b = (1, 2)
print a
## 1
print b
## 2

Du coup on peut faire des fonctions qui semblent (mais c’est une illusion), retourner plusieurs valeurs à la fois :

def foo():
    return 1, 2

print foo()
## (1, 2)
print type(foo())
## 
a, b = foo()
print a
## 1
print b
## 2

Mais un jour, au lieu de faire ça :

t = "a" in ("b", "a")
print t
## True

Vous faites ça

t = "a" in "b", "a"
print t
## (False, 'a')

Enfer, damnation et baba au rhum !

Ce n’est pas du tout ce que vous attendiez !

La raison à cela est que la virgule en Python n’est pas un opérateur, c’est un simple séparateur. Votre expression a donc été analysée comme :

("a" in "b"), "a"

C’est pour cela qu’utiliser les parenthèses est recommandé avec les tuples : pour clarifier la priorisation des opérations. Car comme la virgule n’est pas un opérateur, elle n’a pas de priorité définie comme pour *, /, and et or, et c’est un peu la fête du slip.

Moralité : à part pour l’unpacking, utilisez toujours des parenthèses dans vos tuples.

Ça vous évitera des aventures du genre :

s = "Ceci est un %s %s" % "test", "idiot"
## TypeError: not enough arguments for format string
print s

s = "Ceci est un %s" % "test", "idiot"
print s
## ('Ceci est un test', 'idiot')

s = "Ceci est un %s %s" % ("test", "idiot")
print s
## Ceci est un test idiot

Télécharger le code source de ce tuto

]]>
http://sametmax.com/la-virgule-nest-pas-un-operateur-en-python/feed/ 5 4379
L’opérateur splat (l’étoile: *) en Python http://sametmax.com/operateur-splat-ou-etoile-en-python/ http://sametmax.com/operateur-splat-ou-etoile-en-python/#comments Fri, 01 Jun 2012 18:34:59 +0000 http://sametmax.com/?p=173 *, dit opérateur "splat") en Python est très simple, mais certains cas sont peu intuitifs. Les nouveaux venus ont souvent besoin d'un peu plus d'explications que ce que donne la doc. Les utilisateurs d'autres langages sont généralement déroutés car ils sont habitués certaines fonctionnalités qu'on ne retrouvent pas en Python. ]]> Cet article a été mis à jour et contient maintenant du code Python en version 3

L’utilisation du signe étoile (*, dit opérateur « splat ») en Python est très simple, mais certains cas sont peu intuitifs. Les nouveaux venus ont souvent besoin d’un peu plus d’explications que ce que donne la doc. Les utilisateurs d’autres langages sont généralement déroutés, car ils sont habitués à certaines fonctionnalités qu’on ne retrouve pas en Python.

Ce que * ne permet pas de faire

Il n’y a pas de pointeur en Python, et les passages par référence sont automatiques. Du coup :

mon_objet = MaClasse()
mon_pointeur = *mon_objet
ma_valeur = **mon_pointeur

N’existe pas en Python. On ne peut pas récupérer un pointeur. On ne peut pas choisir si l’on passe une variable par valeur ou par référence. Tout est automatique et transparent.

Les usages basiques de *

La multiplication et la puissance fonctionnent comme on l’attend :

>>> print(2*3) # multiplier 2 par 3
6
>>> print(2**3) # élever 2 à la puissance 3
8

Mais déjà, Python se démarque du lot, car l’opérateur * est surchargé par défaut, et peut s’appliquer aux chaines de caractères et aux listes. Pour les chaines, c’est simple :

>>> print("a" * 3) # on peut multiplier une chaîne par un nombre, et cela donne une chaîne
aaa
>>> print("a" ** 3) # ça ne marche pas avec les puissances
TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'

Pour les listes, c’est plus subtil. Une liste de nombres se multiplie sans y penser :

>>> l = [0, 1, 3]
>>> print(l * 2)
 # on peut multiplier une liste par un nombre, cela donne une liste
[0, 1, 3, 0, 1, 3]
>>> print(l ** 2)
 # ça ne marche pas avec les puissances
TypeError: unsupported operand type(s) for ** or pow(): 'list' and 'int'

En revanche, multiplier une liste d’objets modifiables ne fait que répéter la référence vers cet objet :

>>> l = [{}]
 # on fait une liste contenant un dictionnaire
>>> dicos = l * 3
 # on peut multiplier une liste par un nombre, cela donne une liste
>>> print(dicos)
[{}, {}, {}]

On a l’impression que le comportement est le même que précédemment, en fait pas du tout. Ici on a pas une liste de 3 dictionnaires, mais une liste de 3 références vers le même dictionnaire. Si on modifie le premier élément de la liste, la modification se voit partout :

>>> d = dicos[0] # on récupère ce qu'on croit être le premier dictionnaire
>>> d["Nouvelle cle"] = "Nouvelle valeur" # on le modifie
>>> print(dicos) # afficher la liste montre que les 3 dictionnaires sont en fait un seul et même objet
[{'Nouvelle cle': 'Nouvelle valeur'},
 {'Nouvelle cle': 'Nouvelle valeur'},
 {'Nouvelle cle': 'Nouvelle valeur'}]

Plus pragmatiquement, on peut juste vérifier que c’est la même référence en utilisant la fonction id() :

 >>> id(d[0])
    139656035401544
>>> id(d[1]) # même dico !
    139656035401544
>>> id({}) # dico différent
    139655914013832

Moralité, * sur une liste fait rarement ce qu’on veut.

Unpacking

Python intègre une fonctionnalité, l’unpacking, qui permet de prendre chaque élément d’un itérable et de les attribuer à des variables distinctes, d’un seul coup. C’est un raccourci très pratique :

>>> drapeau = ("bleu", "blanc", "rouge") # ici on utilise un tuple, mais ça marche avec tout itérable
>>> premiere_couleur = drapeau[0]
>>> deuxieme_couleur = drapeau[1]
>>> troisieme_couleur = drapeau[2]
>>> print(premiere_couleur)
'bleu'
>>> print(deuxieme_couleur)
'blanc'
>>> print(troisieme_couleur)
'rouge'
>>> couleur1, couleur2, couleur3 =  drapeau # la même opération, en une ligne grâce à l'unpacking
>>> print(couleur1)
'bleu'
>>> print(couleur2)
'blanc'
>>> print(couleur3)
'rouge'

Vous n’avez rien à faire, l’unpacking est automatique : il suffit de mettre à gauche du signe = le même nombre de variables qu’il y a d’éléments dans la séquence à droite du signe =. Dans le cas contraire, Python râle :

>>> un, deux = drapeau
ValueError: too many values to unpack
>>> un, deux, trois, quatre = drapeau
ValueError: need more than 3 values to unpack

Quel rapport avec * ?

Et bien d’abord, il permet de gérer ce cas où il y a plus d’éléments que de variables en disant « je veux que cette variable contienne le reste »:

couleur1, *autres_couleurs = drapeau
>>> couleur1
    'bleu'
>>> autres_couleurs
    ['blanc', 'rouge']
>>> *autres_couleurs, derniere_couleur = drapeau
>>> autres_couleurs
    ['bleu', 'blanc']
>>> derniere_couleur
    'rouge'

Ensuite, il permet de forcer l’unpacking dans le cas où c’est ambigu.

Faisons une petite fonction de test qui ne fait qu’afficher chacun de ses paramètres :

>>> def afficher_trois_elements(elem1, elem2=None, elem3=None):
...     print(elem1)
...     print(elem2)
...     print(elem3)
...
...
>>> afficher_trois_elements(drapeau)
('bleu', 'blanc', 'rouge')
None
None

Passer drapeau affiche logiquement le tuple comme premier paramètre, et ensuite les valeurs par défaut du premier et du second paramètre.

En utilisant *, nous pouvons forcer l’unpacking de telle sorte que les valeurs du tuple soient passées individuellement comme autant de paramètres :

>>> afficher_trois_elements(*drapeau)
bleu
blanc
rouge

Très pratique quand vous utilisez une collection tout au long du programme pour vous éviter de sans cesse trainer des variables intermédiaires. D’autant que ça marche combiné aux slices :

>>> l = [1, 2, 3, "element que l'on ne veut pas"]
>>> afficher_trois_elements(*l[:-1])
1
2
3

Encore mieux, on peut utiliser ** pour forcer l’unpacking des dictionnaires. Les valeurs du dictionnaire deviennent les valeurs des paramètres, mais cette association se fait par nom : chaque clé du dictionnaire doit correspondre à un nom de paramètre. Ainsi :

>>> elements = {"elem1": "eau", "elem2": "feu", "elem3": "air"}
 # les clés ont le bon nom
>>> afficher_trois_elements(**elements)
eau
feu
air

Si une clé ne possède pas le nom adéquat, tout plante :

>>> elements = {"elem1": "eau", "elem2": "feu", "rien_a_voir": "air"}
>>> afficher_trois_elements(**elements)
TypeError: afficher_trois_elements() got an unexpected keyword argument 'rien_a_voir'

Une autre erreur courante est d’utiliser * avec un dictionnaire. Dans ce cas l’unpacking fonctionne, mais comme itérer sur un dictionnaire donne une liste de clés, c’est comme si vous passiez une liste en paramètres contenant les clés :

>>> elements = {"elem1": "eau", "elem2": "feu", "elem3";: "air"}
>>> afficher_trois_elements(*elements)
elem2
elem3
elem1

Si vous donnez moins de valeurs qu’il n’y a de paramètres, Python remplit tout ce qu’il peut :

>>> afficher_trois_elements(*drapeau[:-1])
bleu
blanc
None
>>> elements = {"elem1": "eau"}
>>> afficher_trois_elements(**elements)
eau
None
None

Dans le cas inverse – si il y a plus d’éléments que de paramètres – Python vous envoie vous brosser :

>>> forces = ("rouge", "bleu", "jaune", "rose", "vert")
>>> afficher_trois_elements(*forces)
TypeError: afficher_trois_elements() takes at most 3 arguments (5 given)

Paramétrage dynamique

Attention, cet usage est souvent confondu avec celui qu’on vient de voir dans la partie précédente. Ils ne font pas du tout la même chose !

Il est parfois pratique de définir une fonction qui accepte un nombre infini de paramètres. Exemple bidon, une fonction qui multiplie ses arguments entre eux :

>>> def multiply(a, b):
...     return a * b # attention, là on utilise * pour multiplier, ne cherchez rien de compliqué ;-)
...
>>> print(multiply(2, 3))
6

Bien sûr, si on veut rajouter un troisième paramètre, il faut la réécrire. Pareil pour un quatrième. Finalement, on finit par demander de passer une liste pour permettre un nombre arbitraire :

>>> def multiply(elements_a_multiplier):
...     res = 1
...     for i in elements_a_multiplier:
...         res = res * i
...     return res
...
>>> multiply((1, 2, 3, 4))
24

Et bien sachez qu’il existe une autre possibilité, autoriser le passage d’une infinité de paramètres ! Cela se fait bien sûr avec *.

>>> def multiply(*tous_les_elements): # on ne change pas grand chose, on rajoute juste *
...     res = 1
...     for i in tous_les_elements:
...         res = res * i
...     return res
...
>>> multiply(1, 2, 3)
 # mais plus besoin d'une séquence !
26
>>> multiply(1, 2, 3, 4, 5)
120

Comment ça marche ? C’est simple, tous les arguments sont automatiquement stockés dans une liste, et cette liste est le paramètre que l’on a désigné par *.

Ce système très puissant peut être utilisé conjointement avec des paramètres normaux :

>>>def afficher(elem1, elem2, *elemx):
...    print(elem1)
...    print(elem2)
...    for e in elemx:
...        print("(*) %s" % e)
…
>>> afficher("Toi", "Moi", "Luke", "Anakin", "Obi Wan", "Robert")
Toi
Moi
(*) Luke
(*) Anakin
(*) Obi Wan
(*) Robert

La seule condition est de mettre * sur un paramètre situé après tous les autres. * est toujours en dernier, et il n’apparait qu’une seule fois. Enfin, il existe une convention pour le nom de cet argument : *args.

Bonne nouvelle, on peut utiliser aussi **. Comme on peut s’y attendre, il permet de récupérer aussi une infinité de paramètres, mais sous forme de dictionnaire. Cela signifie qu’il ne récupère que les paramètres nommés :

>>> def afficher_recette(recette, **ingredients): # ingrédients sera un dictionnaire
...     print(recette)
...     for ingredient in ingredients.items():
...         print(" - %s: %s" % ingredient)
...
>>> afficher_recette("moukraines à la glaviouse",
...                  creme="trop", # on doit donner le nom de ce paramètre
...                  moukraines= "suffisamment",
...                  glaviouse="si disponible") # mais l'ordre des paramètres importe peu
moukraines à la glaviouse
 - glaviouse : si disponible
 - creme : trop
 - moukraines : suffisamment

Il faut également mettre ** après tous les autres arguments. La convention pour nommer ce paramètre est **kwargs, pour « keyword arguments ». Enfin, on peut mélanger tout ça d’un coup :

>>> def affichage_hybride(parametre_normal,
...                       parametre_avec_default="valeur par défaut",
...                       *args,
...                       **kwargs):
...     print(parametre_normal)
...     print(parametre_avec_default)
...     print(args)
...     print(kwargs)
...
>>> affichage_hybride("param1", "param2", "infini1", "infini2", kwinfini1=1, kwinfini2=2)
param1
param2
('infini1', 'infini2')
{'kwinfini1': 1, 'kwinfini2': 2}

On doit absolument mettre les paramètres dans cet ordre :

  1. paramètres normaux et obligatoires;
  2. paramètres normaux facultatifs (valeur par défaut);
  3. paramètres dynamiques;
  4. paramètres dynamiques nommés.

En plus, cela permet en effet de faire jouer les valeurs par défaut de manière très souple :

>>> affichage_hybride("param tout seul")
param tout seul
valeur par défaut
()
{}

Enfin, on peut définir des paramètres qui ne peuvent être passés qu’en spécifiant leur nom. On les appelle les « keyword only parameters ». Pour ce faire, il faut mettre au moins un *, et tout ce qu’il y a après sans étoile ne peut plus être passé comme argument positionnel :

>>> def coooool(normal, *args, keyword_only):
...    print(normal, args, keyword_only)
>>> coooool("yeah", "cool", "man")
Traceback (most recent call last):
  File "", line 1, in 
TypeError: coooool() missing 1 required keyword-only argument: 'keyword_only'
>>> coooool("yeah", "cool", keyword_only="man")
yeah ('cool',) man

Si vous n’avez pas un *args à placer, on peut mettre l’étoile toute seule :

>>> def coooool(normal, *, keyword_only):
...    print(normal, keyword_only)
>>> coooool("yeah", "man")
Traceback (most recent call last):
  File "", line 1, in 
TypeError: coooool() takes 1 positional argument but 2 were given
>>> coooool("yeah", keyword_only="man")
yeah man

Python 3.5

Je mets à jour cet article un mois avant la sortie annoncée de Python 3.5, qui parmi ses nouvelles fonctionnalités, rajoute encore des super pouvoirs à l’opérateur splat.

On peut faire de l’unpacking directement dans les littéraux :

>>> [1, 2, *range(3), *range(2)]
[1, 2, 0, 1, 2, 0, 1]
>>> (*range(1), 4)
(0, 4)
>>> d = {1: 2}
>>> d2 = {3: 4}
>>> {**d, **d2}
{1: 2, 3: 4}

On peut utiliser plusieurs fois l’unpacking des arguments dans un même appel :

>>> def pouet(a, b, c, d):
...    print(a, b, c, d)
>>> couleurs = ('ocre', 'moutarde')
>>> pouet(*couleurs, *range(2)) # double étoile !
ocre moutarde 0 1

Jouons un peu

Si vous vous sentez à l’aise avec tout ça, vous pouvez mélanger plusieurs usages de * d’un coup. Je vous laisse donc en guise de conclusion un petit combo qui utilise un code précédent :

>>> def multiply(*args):
...     res = 1
...     for i in args:
...         res = res * i
...     return res
...
>>> print(multiply(*([2]*6)) == 2**6)
True
]]>
http://sametmax.com/operateur-splat-ou-etoile-en-python/feed/ 42 173