Aller au contenu principal

3 articles tagués avec « sam et max »

Voir tous les tags

· 5 minutes de lecture
Jonas Turbeaux

/img/blog/python-gen.jpg

Python - Comprehension Lists

Dans le top 10 des raisons d’aimer Python se hisse aisément les listes en intension, ou “comprehension lists” pour les gens branchés.

Rappel du concept, et un petit tour complet de ce qu’on peut en faire. Les connaisseurs attendront le second article qui aborde des notions avancées, et contiendra quelques bonus.

On continue de ressusiter les articles de Sam et Max tout en se formant ? C'est parti !

La boucle for

Disclaimer: pour comprendre ce petit gros article, il faut être à l’aise avec la boucle for et les listes.

En Python, on itère beaucoup, c’est à dire qu’on applique très souvent un traitement à tous les éléments d’une séquence, un par un. Et pour ça il y a la boucle for:

sequence = ["a", "b", "c"]
for element in sequence:
print(element)
# a
# b
# c

Et très souvent, on fait une nouvelle liste avec les éléments de la première liste, mais modifiés:

sequence = ["a", "b", "c"]
new_sequence = []
for element in sequence:
new_sequence.append(element.upper())

print(new_sequence)
# ['A', 'B', 'C']

Les listes en intension: la base

Cette opération – prendre une séquence, modifier les éléments un par un, et faire une autre liste avec – est très commune. Et comme pour à peu près tout ce qui est opération courante, Python possède une manière élégante de le faire plus vite.

Reliez bien le bloc précédent, il devient:

sequence = ["a", "b", "c"]
new_sequence = [element.upper() for element in sequence]
print(new_sequence)
# ['A', 'B', 'C']

Il n’y a aucun mystère, ce code fait exactement la même chose, mais:

new_sequence = []
for element in sequence:
new_sequence.append(element.upper())

Est réduit à:

new_sequence = [element.upper() for element in sequence]

Ne cherchez pas un truc compliqué, c’est juste une question de syntaxe, ça fait la même chose, mais écrit différemment : à droite, la boucle, à gauche, ce que l’on veut mettre dans la liste finale.

Et c’est surtout beaucoup plus court.

Là où ça devient franchement sympa, c’est que l’on peut assigner le résultat d’une liste en intension directement à la variable originale:

sequence = ["a", "b", "c"]
new_sequence = [element.upper() for element in sequence]
print(new_sequence)
# ['A', 'B', 'C']

Devient alors:

sequence = ["a", "b", "c"]
sequence = [element.upper() for element in sequence]
print(sequence)
# ['A', 'B', 'C']

Et vous avez du coup un moyen très propre de transformer toute une liste. Listes en intension avancées

On peut faire bien plus avec les listes en intension. Python est un langage dynamiquement typé, donc on peut transformer carrément le type de liste.

sequence = [1, 2, 3]
print([str(nombre) for nombre in sequence])
# ['1', '2', '3']

On peut aussi faire des opérations un peu plus complexes:

sequence = [1, 2, 3]
print(['a' * nombre for nombre in sequence])
# ['a', 'aa', 'aaa']

Et même construire des sequences imbriquées à la volée:

sequence = [1, 2, 3]
print(list(range(5))) # petit rappel de l'usage de la fonction range
# [0, 1, 2, 3, 4]

sequence = [(nombre, list(range(nombre))) for nombre in sequence]
print(sequence)
# [(1, [0]), (2, [0, 1]), (3, [0, 1, 2])]

print(sequence[-1])
# (3, [0, 1, 2])

print(sequence[-1][0])
# 3

print(sequence[-1][1])
# [0, 1, 2]

La syntaxe [expression for element in sequence] autorise n’importe quelle expression, du coup on peut créer des listes très élaborées, en utilisant tous les opérateurs mathématiques, logiques, etc, et toutes les fonctions que l’on veut. Filtrer avec les listes en intension

Une autre opération courante consiste à filtrer la liste plutôt que de la transformer :

nombres = range(10)
nombres_pairs = []
for nombre in nombres:
if nombre % 2 == 0: # garder uniquement les nombres pairs
nombres_pairs.append(nombre)

print(nombres)
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

print(nombres_pairs)
# [0, 2, 4, 6, 8]

Évidement Python a également une syntaxe plus courte pour cela. Il suffit de rajouter la condition à la fin:

nombres = range(10)
print([nombre for nombre in nombres if nombre % 2 == 0])
# [0, 2, 4, 6, 8]

Toutes les expressions habituellement utilisables pour tester une condition sont également disponibles.

Bien sûr, rien ne vous empêche de filtrer ET de transformer la liste en même temps. En clair, un nouvel arrivant à Python fera ça:

nombres = range(10)
sommes = []
for nombre in nombres:
if nombre % 2 == 0:
somme = 0
for i in range(nombre):
somme += i
sommes.append(somme)

print(sommes)
# [0, 1, 6, 15, 28]

Un codeur qui trouve ses marques fera ça:

sommes = []
for nombre in range(10):
if nombre % 2 == 0:
sommes.append(sum(range(nombre)))

print(sommes)
# [0, 1, 6, 15, 28]

Un pythoniste affranchi ira droit au but:

print([sum(range(nombre)) for nombre in range(10) if nombre % 2 == 0])

Bon, en vérité il fera plutôt:

[sum(range(nombre)) for nombre in range(0, 10, 2)]

Mais c’était pour l’exemple :-)

Les listes en intension ont encore plus à offrir, la suite au prochain article !

· 7 minutes de lecture
Jonas Turbeaux

Il y a un blog que j'aimais farfouiller de temps en temps lors de mes débuts en python : Celui de Sam & Max, disparu aujourd'hui et plus accessible. Je me permets de resortir quelques archives qui m'ont aidé à l'époque, et qui pourront très probablement vous aider à votre tour.

L’unpacking

Dans ce premier d'une, je l'espère, longue série d'articles sur python, parlons d'une des 5 choses obligatoire à apprendre en python d'après feu le bog Sam&Max.

L’unpacking est une 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. Et chez Code Commun, on aime tout ce qui peut aider à rendre nos logiciels libre plus lisible.

/img/blog/python-unboxing.jpg

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

· 2 minutes de lecture
Jonas Turbeaux
  • Auteur : Sam et Max, Bloggeur français à propos de Python, Django, Linux, Open Source, Free Software, etc.
  • Article original : Hacker News

/img/blog/hypermedia/original.png

Je suis heureux de constater que les approches minimalistes, comme svelte, htmx et alpine.js sont de plus en plus prisées.

J'ai eu l'impression de me battre seul pendant les années dorées de node, webpack et react où tout le monde créait des piles folles et ajoutait GraphQL et ainsi de suite, pour essentiellement obtenir ce que Django + jquery ont fait il y a 10 ans en un dixième du temps et du code.

Jusqu'à présent, j'ai également survécu [à ces déclarations ]:

  • xml est l'avenir
  • utilisons nosql pour toutes les choses
  • il faut utiliser le même langage pour le back comme pour le front
  • oui, votre site doit avoir une version AMP (ah, vous l'avez oubliée celle-là, n'est-ce pas ? C'était tellement important, et puis pouf, c'est parti comme une larme sous la pluie)
  • oui, votre page d'accueil doit être une SPA
  • vous ne pouvez rien coder sans async
  • vous ne pouvez pas vivre sans une file d'attente de messages
  • tout doit devenir un micro-service
  • bien sûr, vous avez besoin d'un conteneur pour cela
  • il faut bien sûr un orchestrateur pour organiser ces conteneurs
  • bien sûr, vous avez besoin du nuage, il serait insensé de s'occuper soi-même de ces conteneurs et de ces orchestrateurs
  • Hey, pourquoi as-tu un serveur ? Utilise un backend sans serveur !
  • Hey, pourquoi as-tu un backend ? Il suffit de l'appeller depuis une plateforme Saas !

Chaque année, une génération d'ingénieurs doit apprendre les concepts suivants : "il n'y a pas de solution miracle", " utilisez la bonne technologie pour le bon problème", "vous n'êtes pas Google", "réécrire une base de code tous les deux ans n'est pas une bonne décision commerciale", "les choses coûtent de l'argent".