Vue d'ensemble

Python 3.4, publié le 16 mars 2014, est une version charnière qui pose les fondations de la programmation asynchrone en Python. Le module asyncio fait son entrée dans la bibliothèque standard, ouvrant la voie à un écosystème complet de frameworks asynchrones.

Cette version apporte également enum pour définir des énumérations propres, pathlib pour manipuler les chemins de fichiers de manière orientée objet, le module statistics, et surtout l'intégration de pip via ensurepip.

Nouveautés majeures

asyncio (PEP 3156)

Le module asyncio fournit une boucle événementielle et des coroutines pour écrire du code concurrent mono-thread. Dans Python 3.4, les coroutines sont définies avec le décorateur @asyncio.coroutine et le mot-clé yield from. Cette approche sera simplifiée dans Python 3.5 avec async/await.

python
import asyncio

# Coroutine qui simule la récupération de données
@asyncio.coroutine
def recuperer_donnees(source, delai):
    """Simule une requête réseau avec un délai variable."""
    print(f"Début de la requête vers {source}...")
    yield from asyncio.sleep(delai)
    donnees = {"source": source, "resultats": delai * 100}
    print(f"Réponse reçue de {source}.")
    return donnees

# Coroutine qui orchestre plusieurs requêtes en parallèle
@asyncio.coroutine
def agreger_resultats():
    """Lance plusieurs requêtes simultanément et agrège les résultats."""
    taches = [
        recuperer_donnees("api_utilisateurs", 2),
        recuperer_donnees("api_produits", 1),
        recuperer_donnees("api_commandes", 3),
    ]
    # gather lance toutes les tâches en parallèle
    resultats = yield from asyncio.gather(*taches)
    total = sum(r["resultats"] for r in resultats)
    print(f"Total agrégé : {total}")
    return resultats

# Exécution de la boucle événementielle
boucle = asyncio.get_event_loop()
resultats = boucle.run_until_complete(agreger_resultats())
boucle.close()

# Les 3 requêtes s'exécutent en ~3s au lieu de 6s en séquentiel
for r in resultats:
    print(f"  {r['source']} -> {r['resultats']} enregistrements")

enum (PEP 435)

Le module enum permet de définir des types énumérés, c'est-à-dire des ensembles de constantes nommées. IntEnum crée des énumérations compatibles avec les entiers, ce qui est utile pour interagir avec du code existant ou des protocoles numériques.

python
from enum import Enum, IntEnum

# Machine à états pour un système de commande
class EtatCommande(Enum):
    BROUILLON = "brouillon"
    CONFIRMEE = "confirmee"
    EN_PREPARATION = "en_preparation"
    EXPEDIEE = "expediee"
    LIVREE = "livree"
    ANNULEE = "annulee"

# Transitions autorisées entre états
TRANSITIONS = {
    EtatCommande.BROUILLON: [EtatCommande.CONFIRMEE, EtatCommande.ANNULEE],
    EtatCommande.CONFIRMEE: [EtatCommande.EN_PREPARATION, EtatCommande.ANNULEE],
    EtatCommande.EN_PREPARATION: [EtatCommande.EXPEDIEE],
    EtatCommande.EXPEDIEE: [EtatCommande.LIVREE],
    EtatCommande.LIVREE: [],
    EtatCommande.ANNULEE: [],
}

class Commande:
    def __init__(self, numero):
        self.numero = numero
        self.etat = EtatCommande.BROUILLON

    def transiter(self, nouvel_etat):
        """Change l'état de la commande si la transition est valide."""
        if nouvel_etat in TRANSITIONS[self.etat]:
            ancien = self.etat
            self.etat = nouvel_etat
            print(f"Commande {self.numero} : {ancien.value} -> {nouvel_etat.value}")
        else:
            print(f"Transition interdite : {self.etat.value} -> {nouvel_etat.value}")

cmd = Commande("CMD-2014-001")
cmd.transiter(EtatCommande.CONFIRMEE)       # OK
cmd.transiter(EtatCommande.EN_PREPARATION)  # OK
cmd.transiter(EtatCommande.ANNULEE)         # Transition interdite

# IntEnum pour les codes HTTP
class CodeHTTP(IntEnum):
    OK = 200
    CREE = 201
    NON_TROUVE = 404
    ERREUR_SERVEUR = 500

# Utilisable directement comme entier
print(CodeHTTP.NON_TROUVE == 404)  # True
print(CodeHTTP.OK + 1)             # 201

pathlib (PEP 428)

Le module pathlib propose une approche orientée objet pour manipuler les chemins de fichiers. L'opérateur / permet de combiner des chemins de manière intuitive, remplaçant avantageusement les appels à os.path.join().

python
from pathlib import Path

# Comparaison avec os.path
import os.path

# Ancienne manière (os.path)
chemin_ancien = os.path.join(os.path.expanduser("~"), "projets", "mon_app")
existe = os.path.isdir(chemin_ancien)

# Nouvelle manière (pathlib)
chemin = Path.home() / "projets" / "mon_app"
existe = chemin.is_dir()

# Navigation dans l'arborescence d'un projet
racine = Path("/var/www/mon_site")
config = racine / "config" / "settings.toml"
logs = racine / "logs"

print(config.name)      # settings.toml
print(config.stem)      # settings
print(config.suffix)    # .toml
print(config.parent)    # /var/www/mon_site/config

# Lister tous les fichiers Python d'un projet
projet = Path(".")  # répertoire courant
fichiers_py = sorted(projet.rglob("*.py"))
for f in fichiers_py[:5]:
    taille = f.stat().st_size
    print(f"  {f} ({taille} octets)")

# Lecture et écriture simplifiées
fichier_sortie = Path("resultat.txt")
fichier_sortie.write_text("Traitement terminé avec succès.\n", encoding="utf-8")
contenu = fichier_sortie.read_text(encoding="utf-8")
print(contenu)  # Traitement terminé avec succès.

# Création de répertoires imbriqués
dossier = Path("sortie") / "rapports" / "2024"
dossier.mkdir(parents=True, exist_ok=True)

Module statistics

Le nouveau module statistics fournit des fonctions pour calculer des statistiques descriptives de base : moyenne, médiane, écart-type, variance, etc. C'est un ajout bienvenu pour les analyses rapides sans dépendance externe.

python
import statistics

# Analyse des temps de réponse d'une API (en millisecondes)
temps_reponse = [45, 52, 48, 120, 47, 51, 49, 200, 46, 53, 50, 48]

print(f"Moyenne     : {statistics.mean(temps_reponse):.1f} ms")
print(f"Médiane     : {statistics.median(temps_reponse):.1f} ms")
print(f"Écart-type   : {statistics.stdev(temps_reponse):.1f} ms")
print(f"Variance    : {statistics.variance(temps_reponse):.1f}")

# La médiane est plus résistante aux valeurs aberrantes
# que la moyenne (120 ms et 200 ms tirent la moyenne vers le haut)

# Analyse de la distribution des notes d'un examen
notes = [8, 10, 10, 11, 12, 12, 12, 13, 14, 14, 15, 16, 18]

print(f"\nNotes de l'examen :")
print(f"Moyenne     : {statistics.mean(notes):.1f}")
print(f"Médiane     : {statistics.median(notes):.1f}")
print(f"Mode        : {statistics.mode(notes)}")
print(f"Basse méd.  : {statistics.median_low(notes)}")
print(f"Haute méd.  : {statistics.median_high(notes)}")

pip intégré (ensurepip)

Le module ensurepip garantit que pip est disponible dans chaque installation de Python 3.4+. Auparavant, il fallait télécharger et exécuter manuellement un script d'installation. Ce changement simplifie considérablement le déploiement et la prise en main de Python.

python
# Vérifier que pip est disponible
# python -m ensurepip --upgrade

# Utilisation standard de pip
# python -m pip install requests
# python -m pip install --upgrade pip
# python -m pip list

# Script de déploiement qui s'assure que pip est présent
import subprocess
import sys

def assurer_pip():
    """Vérifie et installe pip si nécessaire."""
    try:
        import pip
        print(f"pip {pip.__version__} est déjà installé.")
    except ImportError:
        print("pip absent, installation via ensurepip...")
        import ensurepip
        ensurepip.bootstrap(upgrade=True)
        print("pip installé avec succès.")

def installer_dependances(paquets):
    """Installe une liste de paquets via pip."""
    for paquet in paquets:
        subprocess.check_call([
            sys.executable, "-m", "pip", "install",
            "--quiet", paquet
        ])
        print(f"  {paquet} installé.")

assurer_pip()
# installer_dependances(["requests", "click", "rich"])

Améliorations mineures

  • Le module tracemalloc permet de suivre les allocations mémoire de l'interpréteur.
  • functools.singledispatch permet la surcharge de fonctions basée sur le type du premier argument.
  • Le module selectors fournit une abstraction haut niveau pour le multiplexage d'E/S.
  • hashlib est enrichi de nouvelles fonctions de hachage (BLAKE2, SHA-3).
  • Le module dis améliore l'affichage du bytecode désassemblé.

Dépréciations et suppressions

Changements notables :

  • Le module asyncio est marqué comme provisoire (API susceptible de changer).
  • Le format imp est déprécié au profit de importlib.
  • Le module formatter est déprécié.

Sources