Makina Blog

Le blog Makina-corpus

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+)

Documentation

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+)

Documentation

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+)

Documentation

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+)

PEP 448

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+)

Documentation

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+)

PEP 526

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+)

Documentation

@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+)

Documentation

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+)

Documentation

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 est uninstall
  • 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

Image
Encart PyConFr 2024
21/10/2024

Makina Corpus est spon­sor de la PyConFR 2024

Le soutien de Makina Corpus à la PyConFR 2024, qui se tient du 31 octobre au 3 novembre 2024 à Stras­bourg, reflète ses valeurs de partage et d’in­no­va­tion, et son enga­­ge­­ment envers la commu­nauté dyna­­mique et ouverte de Python.

Voir l'article
Image
Webinaire découverte de Canari
10/04/2024

Revoir les webi­naires : décou­verte de l’ou­til CANARI-France

L’ap­pli­ca­tion CANARI-France est destiné aux acteurs agri­coles afin de calcu­ler des indi­ca­teurs agro-clima­tiques à partir de projec­tions clima­tiques. Décou­vrer en le replay des 4 webi­naires orga­ni­sés par Sola­gro et l’ADEME.

Voir l'article
Image
Python
26/07/2023

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.

Voir l'article

Inscription à la newsletter

Nous vous avons convaincus