Makina Blog
Pourquoi mettre à jour son Python (régulièrement)
Le début des années 2010 a vu des centaines d’articles parler du passage de Python 2 à Python 3. Cet article n’en est pas un.
Python 3.0 est sorti il y a tout juste 10 ans : joyeux anniversaire ! Il s’en est passé des choses depuis… 7 versions majeures !
Bien des distributions Linux proposent une ancienne version de python 3 :
- Debian Jessie (oldstable) : python 3.4.2 (2.7.9 par défaut sur le système)
- Debian Stretch (stable) : python 3.5.3 (2.7.13 par défaut sur le système)
- Ubuntu Xenial (16.04 LTS) : python 3.5.1 (2.7.12 par défaut sur le système)
- Ubuntu Bionic (18.04 LTS) : python 3.6.7 par défaut (2.7.14 disponible également)
- CentOS 7 : 3.6 via SCL (2.7.5 par défaut sur le système)
- Fedora 28 : 3.7.1 et 2.7.15
- ArchLinux : 3.7.1
Ces données datent de l’écriture de cet article.
On remarquera que python 2.7 est toujours aussi présent par défaut, c'est dû à l'utilisation massive dans les outils internes des distributions, dans la gestion du système. Cela n’aide pas pour profiter des derniers ajouts à la stdlib (bibliothèque standard) et des optimisations de performance.
Les bienfaits d’une version de python à jour
Finis les débats stériles à la sauce “Python 2 était plus rapide”, les faits sont là et Python 2 a perdu, mais ce n’est pas le seul argument pour garder à jour Python 3. Au cours de ses différentes versions majeures, il a acquis du sucre syntaxique et une ribambelle de nouveaux modules dans la stdlib.
Pathlib (3.4+)
Grâce à ce module, plus besoin de s’embêter à gérer différemment des chemins Windows et Unix. Au lieu de travailler avec des chaînes, vous travaillez avec des objets :
from pathlib import Path
# Liste les sous-dossiers du chemin courant
p = Path('.')
print([x for x in p.iterdir() if x.is_dir()])
p.exists()
p.chmod(644)
p.rename('myproject')
Grâce à la surcharge d’opérateur, il est possible de concaténer très facilement les chemins :
p = Path('/Users')
username = 'sebcorbin'
child = p / username / 'Documents' / 'data.csv'
Et les propriétés des objets renferment toutes les informations dont vous aurez besoin :
child.parent # PosixPath('/Users/sebcorbin/Documents')
child.name # data.csv
child.suffix # .csv
child.stem # data
enum (3.4+)
Il y a deux façons d’écrire des enumérations :
from enum import Enum
# 1ère possibilité
class State(Enum):
DRAFT = 0
SUBMITTED = 1
ACCEPTED = 2
REFUSED = 3
# 2ème possibilité
State = Enum('State', 'DRAFT SUBMITTED ACCEPTED REFUSED')
Ensuite, elle s’utilise de cette manière article.state = State.DRAFT
Il y a également des enumérations basées sur les drapeaux, qui permettent des opérations binaires :
from enum import Flag, auto
class Seo(Flag):
HAS_TITLE = auto()
HAS_DESCRIPTION = auto()
HAS_SLUG = auto()
HAS_KEYWORDS = auto()
article.seo = Seo.HAS_TITLE | Seo.HAS_SLUG
print(bool(article.seo & Seo.HAS_TITLE)) # True
print(bool(article.seo & Seo.HAS_DESCRIPTION)) # False
La fonction auto()
va se charger de mettre des valeurs entières
puissances de 2, afin d’avoir toujours des valeurs qui permettent les
opérations binaires.
Type hints (3.5+)
Le typing dans python permet de spécifier quels types de variable les fonctions retournent et attendent en paramètre. Vous pouvez également spécifier à python quel type est censé contenir une variable.
# /tmp/test_typing.py
from typing import List
def greeting(name: str) -> str:
return 'Hello ' + name
entiers = [0, 1, 2] # type: List[int]
hoy = greeting(1337)
entiers.append(hoy)
Cette syntaxe, bien qu’elle complique la lisibilité du code permet
d’utiliser des static type checker, tels que
mypy qui permettent de détecter
d’éventuelles erreurs avant même que vous exécutiez du code.
C'est aussi utilisé par les IDE pour vous prévenir si vous donnez
des clous au lieu de carottes à votre fonction lapin.
$ mypy /tmp/test_typing.py
/tmp/test_typing.py:6: error: Argument 1 to "greeting" has incompatible type "int"; expected "str"
/tmp/test_typing.py:7: error: Argument 1 to "append" of "list" has incompatible type "str"; expected "int"
Généralisation de l’\ unpacking (3.5+)
Grâce à cette PEP, vous pouvez unpacker plusieurs fois dans un appel
de fonction print(*[1], *[2], 3)
.
C’est particulièrement utile pour combiner deux dictionnaires :
my_dict1 = {'y': 2}
result_dict = {'x': 1, **my_dict1}
print(result_dict) # {'x': 1, 'y': 2}
f-strings (3.6+)
Les f-strings (chaînes de formatage), une fois qu’on y a goûté, on ne plus s’en passer :
prenom = "Pierre"
nom = "Brémont"
print("Bonjour %s %s !" % (prenom[0], nom))
print("Bonjour {} {} !".format(prenom[0], nom))
print(f"Bonjour {prenom[0]} {nom} !")
Concises, claires, ne nécéssitant pas d'explication : les f-strings ont beaucoup d’avantages et peuvent suffire à vouloir passer à python 3.6.
Variable annotations (3.6+)
Pour palier au fait que les commentaires de typing ne soient pas très lisibles, cette PEP a introduit directement le fait de typer les variables dans le langage :
entiers: List[int] = [0, 1, 2]
hoy: str
@dataclass
(3.7+)
@dataclass
est un décorateur qui permet de raccourcir le temps de
développement de classes simples (souvent destinées à stocker des
données) :
from dataclasses import dataclass
@dataclass
class ElementPanier:
'''Classe pratique pour un inventaire.'''
nom: str
prix_unitaire: float
quantite: int = 0
def cout_total(self) -> float:
return prix_unitaire * quantite
Dans cette classe, le fait d’ajouter le décorateur @dataclass
implique une fonction __init__()
de ce type :
def __init__(self, nom: str, prix_unitaire: float, quantite: int=0):
self.nom = nom
self.prix_unitaire = prix_unitaire
self.quantite = quantite
Il est vrai que ce genre de ligne de code est rébarbatif et on aimerait
souvent s’en dispenser, @dataclass
est donc une bonne nouvelle.
breakpoint()
(3.7+)
Du debug en veux-tu en voilà !
- pdb : le classique
- ipdb : le coloré amélioré
- pdbpp : le coloré amélioré, collant qui remplace le classique
- pudb : le coloré fenêtré
- rpdb : le distant
Une fois que vous avez choisit votre préféré, vous pouvez utiliser
import pdb; pdb.set_trace()
mais c’est long à écrire, donc ces
feignants de développeurs python se sont dit : on va faire une fonction : breakpoint()
!
Celle-ci appelera automatiquement votre débuggeur préféré depuis la
variable d’environnement PYTHONBREAKPOINT
. Il ne vous reste plus
qu’à installer votre debugger et mettre dans votre ~/.bashrc
par
exemple :
export PYTHONBREAKPOINT="ipdb.set_trace"
asyncio (3.7+)
Bon, difficile de vous présenter asyncio en un petit chapitre, mais si vous avez des entrées/sorties dans votre programme, ou des choses qui prennent du temps, rien de mieux que l’asynchrone pour éviter d’attendre que les requêtes partent ou reviennent.
import asyncio
import time
async def dire_apres(delai, quoi):
await asyncio.sleep(delai)
print(quoi)
async def main():
tache1 = asyncio.create_task(
dire_apres(1, 'bonjour')
)
tache2 = asyncio.create_task(
dire_apres(2, 'le monde')
)
print(f"démarré à {time.strftime('%X')}")
# On attend que les tâches soient terminées
await tache1
await tache2
print(f"fini à {time.strftime('%X')}")
asyncio.run(main())
démarré à 22:11:04
bonjour
le monde
fini à 22:11:06
Comment garder python à jour avec pyenv
L’outil le plus pratique pour installer une version spécifique de python est de loin pyenv. Il permet d’avoir une version python globale, modifiable à tout moment, ainsi que de définir une version de python à utiliser dans un dossier. Son rôle est aussi de compiler et centraliser les différentes versions de python que vous lui demanderez.
Ensuite, voici quelques commandes bien utiles (la documentation complète se trouve ici) :
-
pyenv install <version>
compile une version spécifique de python (utiliser--list
pour lister quelles versions sont disponibles à la compilation, son pendant estuninstall
-
pyenv versions
permet d’afficher les versions qui ont été compilées -
pyenv global <version>
définit une version globale -
pyenv local <version>
définit une version locale au dossier courant (écrite dans un fichier.python-version
), utiliser--unset
ou retirer le fichier pour annuler -
pyenv version
vous affiche quelle est la version de python actuelle et comment il l’a déduite (locale/globale)
Une fois votre version compilée et définie, il ne vous reste plus qu’à
créer votre virtualenv
avec celle-ci.
Conclusion
Fini le temps où la communauté Python se battait entre deux versions : désormais, il faudra maintenir un python à jour !
Actualités en lien
Makina Corpus est sponsor de la PyConFR 2024
Le soutien de Makina Corpus à la PyConFR 2024, qui se tient du 31 octobre au 3 novembre 2024 à Strasbourg, reflète ses valeurs de partage et d’innovation, et son engagement envers la communauté dynamique et ouverte de Python.
Revoir les webinaires : découverte de l’outil CANARI-France
L’application CANARI-France est destiné aux acteurs agricoles afin de calculer des indicateurs agro-climatiques à partir de projections climatiques. Découvrer en le replay des 4 webinaires organisés par Solagro et l’ADEME.
La formation Python éligible au CPF est enfin arrivée
Makina Corpus propose un nouvelle formation Python éligible au CPF. Grâce à cette certification, cette formation peut être entièrement financée par votre compte Compte Personnel de Formation.