Pratiques avancées et méconnues en Python

Formation

En Semi-présenciel Paris

Prix sur demande

Appeler le centre

Avez-vous besoin d'un coach de formation?

Il vous aidera à comparer différents cours et à trouver la solution la plus abordable.

Description

  • Typologie

    Formation

  • Méthodologie

    En semi-présentiel

  • Lieu

    Paris

Grâce à cette formation vous pourrez acquérir les connaissances nécessaires qui vous permettrons d’ajouter des compétences à votre profil et obtenir de solides aptitude qui vous offriront de nombreuses opportunités professionnelles.

Les sites et dates disponibles

Lieu

Date de début

Paris ((75) Paris)
Voir plan
7 Cité Paradis, 75010

Date de début

Consulter

Questions / Réponses

Ajoutez votre question

Nos conseillers et autres utilisateurs pourront vous répondre

À qui souhaitez-vous addresser votre question?

Saisissez vos coordonnées pour recevoir une réponse

Nous ne publierons que votre nom et votre question

Les Avis

Le programme

Introduction du cours

Beaucoup de gens pensent maîtriser correctement le langage Python après quelques cours lus, et quelques exercices réalisés.
Cette sensation vient de la simplicité apparente de Python pour un débutant. Pourtant, malgré ce que l'on pense, un code Python est relativement souvent améliorable sur de nombreux points, et c'est sur ces points que l'on distingue un codeur expérimenté d'un codeur débutant. :)

Ce tutoriel recense la plupart de ces points permettant de rendre son code plus lisible et plus pythonnique en utilisant des fonctionnalités méconnues du langage et en l'exploitant à 100 %.

De par sa nature, ce tutoriel ne vise clairement pas un public débutant en Python, mais plutôt un public ayant un peu d'expérience en Python et prêt à se documenter pour en savoir plus sur ce qui y est dit, car rien n'est aussi bien documenté que la documentation elle-même. :)

Bonne lecture à vous.

La fonction enumerate

Un besoin assez courant quand on manipule une liste ou tout autre objet itérable est de récupérer en même temps l'élément et son indice à chaque itération. Pour cela, la méthode habituellement utilisée est simple : au lieu d'itérer sur notre liste, on va itérer sur une liste d'entiers partant de 0 et allant de 1 en 1 jusqu'au dernier indice valide de la liste, obtenue via la fonction xrange. Cette méthode n'est pas du tout efficace : en effet, manipuler ainsi l'indice est totalement contre-intuitif et va à l'encontre du principe des itérateurs en Python.

Un exemple de code utilisant cette mauvaise méthode :

for indice in xrange(0, len(liste)): print "liste[%d] = %r" % (indice, liste[indice])

Pour réaliser ce genre d'itérations, on va utiliser la fonction enumerate : elle permet en effet de récupérer une liste de tuples (indice, valeur) en fonction du contenu de la liste, et d'une manière très pratique. On l'utilise comme ceci :

for indice, valeur in enumerate(liste): print "liste[%d] = %r" % (indice, valeur) # ou sinon, comme enumerate renvoie une liste de tuples : for indval in enumerate(liste): print "liste[%d] = %r" % indval

Cette manière de faire se rapproche beaucoup plus de ce qui doit être fait en Python si l'on veut utiliser correctement le langage : on itère directement sur les valeurs à la sortie d'un générateur, au lieu d'utiliser un indice (manière plus courante dans les langages comme le C n'ayant pas d'itérateurs comme ceux de Python).

Avec enumerate, on peut par exemple écrire très facilement un code numérotant les lignes d'un texte :

texte = file('nom-de-fichier').read() for numberedline in enumerate(texte.split('\n')): print "%d\t%s" % numberedline

Au niveau des performances, l'utilisation de enumerate par rapport à des indices ne change strictement rien, avec une différence de temps d'exécution d'un centième de seconde sur un million d'itérations. Aucun argument n'est donc bon pour continuer à utiliser les indices dans ce genre de cas. :)

Pour plus d'informations : PEP 279.

Les générateurs

Ce qu'on appelle en Python "générateurs" sont des objets que l'on crée d'une manière plutôt originale, en appelant une fonction que l'on crée soi-même et qui possède dans son code l'instruction yield. Cette fonction ne doit retourner aucune valeur.

Un exemple de code créant un générateur est le suivant :

>>> def generateur(): ... while True: yield 1 ... >>> generateur <function generateur at 0x7f1c0bbe0aa0> >>> type(generateur) <type 'function'> >>> generateur() <generator object at 0x7f1c0bbf8710> >>> type(generateur()) <type 'generator'>

Nous avons donc créé un générateur.
C'est beau, mais vous vous demandez sûrement à quoi ces générateurs servent.
Eh bien ce sont des objets itérables, c'est-à-dire qu'ils vont successivement renvoyer différentes valeurs.
Pour récupérer ces différentes valeurs, il y a plusieurs solutions :

  • soit utiliser une boucle classique for ... in ...: ;

  • soit utiliser la méthode next du générateur.

Pour bien comprendre, prenons l'exemple d'une fonction nous permettant, à l'instar de la fonction xrange, de nous donner une liste de nombres de 0 à y.

def range_perso(x, y=0): if y < x: # Si y est inférieur à x on inverse ces deux valeurs x, y = y, x yield x # On renvoie la première valeur de X while x < y: x += 1 yield x # On retourne x

On peut par exemple choisir d'utiliser notre nouvelle fonction / generateurrange_perso(x,y) dans une boucle for :

for n in range_perso(10): print n

Le principe de fonctionnement d'un générateur est simple.

Quand on veut récupérer une valeur, il va commencer à exécuter le code de la fonction, jusqu'au moment où l'exécution rencontrera une instruction yield.
À ce moment-là, le code s'arrête, et on ressort de la fonction en ayant comme valeur la valeur donnée à l'instruction yield rencontrée.
Lorsque l'exécution atteint la fin de la fonction, le générateur envoie une exception de type StopIteration.
Cette exception est :

  • soit à rattraper manuellement en cas d'utilisation de la méthode next du générateur ;

  • soit signifie la fin de la boucle for dans le cas d'une itération par boucle for ... in ...:.

On peut ainsi coder des tonnes de fonctions très pratiques qui renverraient normalement par exemple des listes, en leur faisant renvoyer les valeurs une par une. Par exemple, ce générateur implémente la suite de Syracuse :

def syracuse(n): yield n while n != 1: if n % 2 == 0: n = n / 2 else: n = 3 * n + 1 yield n for n in syracuse(3): print n

Un générateur ne doit pas forcément s'arrêter.
Par exemple, ce générateur part d'un nombre x, et donne toutes les valeurs en partant de x : x + 1, x + 2, x + 3, etc.

def countfrom(x): while True: yield x x += 1 for n in countfrom(10): print n if n > 20: break # Si on ne break pas, on est en boucle infinie # car le générateur ne s'arrête pas.

Avec les générateurs, on peut aussi recoder la fonction enumerate vue précédemment (qui est en réalité un générateur codé en C et intégré à la bibliothèque standard Python).

def enumerate(li): for i in xrange(0, len(li)): yield i, li[i]

La fonction xrange est, elle aussi, un générateur codé en C, mais que l'on peut très facilement programmer en Python :

def xrange(start, stop=None, step=1): if stop is None: start, stop = 0, start while step > 0 and start < stop or step < 0 and start > stop: yield start start += step

Pour plus d'informations : PEP 255

Les coroutines

Les coroutines sont des fonctions utilisant les générateurs de Python, et permettant de réaliser des choses impossibles autrement.
Leur première implémentation en Python a été réalisée par le projet Stackless Python, puis la PEP 342 a intégré tout ce qui est nécessaire à la création de coroutines à CPython, l'interpréteur Python que vous connaissez. :)

Leur principale particularité est de pouvoir mettre en pause leur exécution en renvoyant une valeur (donc, comme un générateur). Pendant cette pause, elles peuvent également recevoir une valeur leur indiquant comment continuer leur exécution. Pour stopper l'exécution de notre coroutine, on va utiliser l'instruction yield vue précédemment pour les générateurs. La différence est que l'on pourra faire des choses comme ceci :

var = yield 42

Notre instruction yieldrenvoie donc une valeur qui lui est donnée pendant sa pause, et qui est par défaut None. Pour assigner une valeur au résultat de yield, on va utiliser la méthode send de notre générateur. On peut ainsi faire interagir notre générateur et le reste de notre programme très facilement, sans utiliser de variables globales.

Un exemple d'utilisation de la méthode send est celui d'un générateur qui compte de 0 à x, auquel on va pouvoir faire modifier la valeur actuelle du compteur, comme ceci :

def compteur(x): n = 0 while n <= x: v = (yield n) # Prenez l'habitude de mettre des parenthèses # autour d'une instruction yield ;) if v is not None: n = v else: n += 1 gen = compteur(25) for i in gen: print i # Si on en est à 15, on veut passer directement à 18 if i == 15: gen.send(18)

Maintenant, voyons plus précisément les coroutines en elles-mêmes : ce sont deux fonctions qui vont interagir entre elles avec ce système de yield et send, et ainsi se communiquer des valeurs tout en gardant un contexte évoluant au fur et à mesure. On peut reprendre l'exemple de la PEP 342 en le simplifiant : un script écrivant les lignes d'un texte une par une dans des fichiers différents :

def ecrire_lignes_vers(combien, destination): """ Écrit `combien` lignes vers `destination`. """ while True: lignes = [ ] plein = False commence = False try: for i in xrange(combien): commence = True lignes.append((yield)) plein = True except GeneratorExit: # On a appelé la méthode close() de notre générateur qui # le fait normalement s'arrêter ici. Si notre liste de # lignes n'est pas pleine, on force son écriture. if not plein and commence: destination.send(lignes) destination.close() return else: # Pas d'exceptions, ça roule, on envoie vers la # destination destination.send(lignes) def ecrire_vers_fichiers(): """ Écrit les lignes vers des fichiers numérotés. """ i = 0 while True: file('decoupe%04d.txt' % i, 'w').write('\n'.join((yield))) i += 1 def decouper_fichier_par(fichier, combien): destination = ecrire_vers_fichiers() ecrivain_lignes = ecrire_lignes_vers(combien, destination) # On initialise les générateurs pour éviter une exception destination.next() ecrivain_lignes.next() for ligne in file(fichier).read().split('\n'): ecrivain_lignes.send(ligne) ecrivain_lignes.close()

Cet exemple montre une jolie utilisation des coroutines pour couper un fichier en fichiers de n lignes numérotées dans l'ordre. Elles permettent également bien d'autres choses, qui sont mieux expliquées sur la page wikipédia anglaise des coroutines.

Pour plus d'informations : PEP 342.

Les list comprehensions et generator expressions

Les list comprehensions et generator expressions sont deux concepts très proches facilitant énormément la manipulation de listes et la création de générateurs simples.
Voyons tout d'abord les list comprehensions, plus faciles à aborder que les generator expressions que nous verrons ensuite.

Il arrive parfois que l'on doive faire le même traitement sur tous les éléments d'une liste, ou qu'on veuille éliminer tous les éléments d'une liste ne satisfaisant pas un prédicat. Pour cela, plusieurs solutions sont possibles :

  • la boucle for : c'est une solution plutôt mauvaise car elle nécessite (si on veut faire quelque chose de correct) une copie de la liste ; elle ne permet pas de travailler directement sur nos éléments ;

  • les méthodes map/filter et les itérateurs imap/ifilter : cette solution est clairement meilleure, du moins en termes de performances ; ces fonctions sont codées en C et travaillent directement sur les éléments de notre liste. Cependant, lorsque le traitement à réaliser n'est pas faisable par une fonction déjà créée, on doit créer cette fonction ou utiliser une fonction lambda, ralentissant ainsi l'exécution et réduisant la lisibilité ;

  • les list comprehensions : elles permettent de récupérer une nouvelle liste avec tous les éléments de l'ancienne liste filtrés et sur lesquels on a effectué (ou non) un traitement. C'est à cette méthode, comme vous l'avez sûrement compris, que l'on va s'intéresser. :)

Une list comprehension a la syntaxe suivante :

[expression for element in liste if predicat]

Le prédicat étant optionnel, la syntaxe peut donc se réduire à ceci :

[expression for element in liste]

expression est une expression Python (donc par exemple un tuple, une liste, une fonction, un type, une list comprehension, ou même plus simplement une somme ou un produit ;) ), qui peut utiliser notre variable element. Cette list comprehension va créer une nouvelle liste avec, à la place de element, la valeur de expression qui peut, bien entendu, dépendre de element comme vous l'aurez sûrement compris. ;)

Voyons maintenant quelques choses réalisables facilement avec les list comprehensions :

>>> liste = range(10) # On prend une liste contenant les 10 premiers entiers naturels >>> liste [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> [element + 1 for element in liste] # La même liste en ajoutant 1 à chaque élément [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] >>> [element ** 2 for element in liste] # On élève les éléments au carré [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] >>> liste, liste == range(10) # La liste n'est pas modifiée ;) ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], True) >>> liste = [element for element in liste if element % 2 == 0] # On prend tous les éléments pairs de notre liste >>> liste [0, 2, 4, 6, 8] >>> [element ** 4 for element in liste if element < 7] # On met à la puissance 4 les éléments inférieurs à 7 [0, 16, 256, 1296]

Maintenant que les list comprehensions n'ont plus de secrets pour vous, voyons leur petite soeur : les generator expressions.
Ces expressions sont très proches des list comprehensions ; la seule différence est que, contrairement aux list comprehensions qui renvoient une liste, la generator expression renvoie un générateur sur lequel on peut itérer pour récupérer les éléments calculés. Cela permet de réaliser la transformation de nos éléments à la volée quand on itére sur notre générateur. ;)

Leur syntaxe est, elle aussi, simplissime :

(expression for element in liste)

On peut également, comme pour les list comprehensions, rajouter une condition avec if predicat au bout.
La seule vraie différence est l'utilisation de parenthèses à la place des crochets. Cela permet d'ailleurs de l'utiliser avec une légère variante syntaxique lors de l'appel d'une fonction :

fonction(expression for element in liste)

Un petit exemple de leur utilisation est le suivant :

>>> liste = range(10) >>>...

Appeler le centre

Avez-vous besoin d'un coach de formation?

Il vous aidera à comparer différents cours et à trouver la solution la plus abordable.

Pratiques avancées et méconnues en Python

Prix sur demande