Python

Python & Pickle : manipuler les fichiers binaires

×

Recommandés

Pickle est la bibliothèque standard de Python pour sérialiser (convertir en octets) et désérialiser (recréer) des objets. C’est rapide, pratique, et fidèle à la structure interne des objets Python… mais ce n’est pas un format d’échange universel ni sécurisé contre les contenus malveillants. Ce guide va à l’essentiel : quand l’utiliser, comment l’utiliser proprement, et comment éviter les pièges.

⚠️ Sécurité
N’ouvrez jamais un fichier pickle dont vous n’êtes pas l’auteur ou que vous ne validez pas cryptographiquement. pickle.load() peut exécuter du code lors du chargement.


1) Quand utiliser (ou éviter) Pickle

À utiliser si…

  • vous devez persister un état Python pour vous-même (caches, modèles entraînés maison, snapshots d’objets) ;
  • vous voulez la fidélité des types Python (list/dict imbriqués, objets personnalisés) ;
  • la vitesse prime sur l’interopérabilité.

À éviter si…

  • vous échangez des données avec d’autres langages/outils → préférez CSV/JSON/Parquet ;
  • vous avez un risque sécurité (fichiers non fiables) ;
  • vous avez besoin d’un format stable “long terme” (les protocoles évoluent).

2) Notions clés

  • Sérialisation : transformation d’un objet en flux d’octets.
  • Protocole : version du format binaire (pickle.HIGHEST_PROTOCOL = le plus efficace).
  • Compatibilité : l’unpickling d’objets personnalisés requiert que le module et la classe existent au moment du chargement (même chemin d’import).

3) Premiers pas : écrire et lire un objet

import pickle

data = {"clients": ["Ali", "Nora"], "total": 245.90, "paid": True}

# Écriture binaire
with open("data.pkl", "wb") as f:
    pickle.dump(data, f, protocol=pickle.HIGHEST_PROTOCOL)

# Lecture binaire
with open("data.pkl", "rb") as f:
    loaded = pickle.load(f)

print(loaded)

Bon réflexe : utilisez systématiquement protocol=pickle.HIGHEST_PROTOCOL.


4) Plusieurs objets dans un même fichier

import pickle

objs = [{"id": 1}, {"id": 2}, {"id": 3}]
with open("multi.pkl", "wb") as f:
    for o in objs:
        pickle.dump(o, f, protocol=pickle.HIGHEST_PROTOCOL)

# Relire jusqu'à la fin du flux
items = []
with open("multi.pkl", "rb") as f:
    try:
        while True:
            items.append(pickle.load(f))
    except EOFError:
        pass

print(items)

5) Compression transparente (gzip/bz2/lzma)

import gzip, pickle

big_array = list(range(200_000))

with gzip.open("data.pkl.gz", "wb") as f:
    pickle.dump(big_array, f, protocol=pickle.HIGHEST_PROTOCOL)

with gzip.open("data.pkl.gz", "rb") as f:
    restored = pickle.load(f)

💡 Astuce perf
Compressez surtout les données “texte” ou peu entropiques. Pour des tableaux numériques lourds, regardez NumPy (.npy/.npz) ou Parquet.


6) Sérialiser des classes personnalisées

from dataclasses import dataclass
import pickle

@dataclass
class Facture:
    numero: str
    client: str
    montant: float
    # Attribut non sérialisable (ex: connexion) -> exclu via __getstate__/__setstate__
    _cache = None

    def __getstate__(self):
        state = self.__dict__.copy()
        state.pop("_cache", None)      # on retire ce qu'on ne veut pas pickler
        return state

    def __setstate__(self, state):
        self.__dict__.update(state)
        self._cache = {}               # on re-initialise au chargement

fact = Facture("F2025-001", "ACME", 1299.0)

with open("facture.pkl", "wb") as f:
    pickle.dump(fact, f, protocol=pickle.HIGHEST_PROTOCOL)

with open("facture.pkl", "rb") as f:
    fact2 = pickle.load(f)
print(fact2)

À retenir

  • Les classes doivent être définies au niveau module (pas à l’intérieur d’une fonction) pour être retrouvées à l’unpickling.
  • __getstate__/__setstate__ permettent de contrôler finement ce qui est stocké.

7) Versionner vos données (migration de schéma)

Comme Pickle reflète vos structures Python, faites évoluer vos objets sans casser :

import pickle

PAYLOAD_VERSION = 2

def dump_factures(path, factures):
    payload = {"version": PAYLOAD_VERSION, "items": factures}
    with open(path, "wb") as f:
        pickle.dump(payload, f, protocol=pickle.HIGHEST_PROTOCOL)

def load_factures(path):
    with open(path, "rb") as f:
        payload = pickle.load(f)
    v = payload.get("version", 1)
    items = payload["items"]
    if v == 1:
        # Migration -> v2 (ex : renommer un champ, ajouter une clé, etc.)
        for it in items:
            it.setdefault("devise", "EUR")
        v = 2
    return items

Bonnes pratiques migration

  • Stockez un champ version au niveau racine.
  • Conservez des fonctions de migration idempotentes.
  • Ajoutez des tests pour chaque migration.

8) Intégrité & authentification (HMAC)

Pickle ne sécurise pas le contenu. Pour s’assurer que le fichier n’a pas été altéré, associez-y une signature HMAC avec une clé secrète :

import hmac, hashlib, pickle

SECRET = b"changez-moi-une-fois-pour-toutes"  # stockez-la hors dépôt git

def dumps_signed(obj: object) -> tuple[bytes, bytes]:
    data = pickle.dumps(obj, protocol=pickle.HIGHEST_PROTOCOL)
    sig  = hmac.new(SECRET, data, hashlib.sha256).digest()
    return data, sig

def loads_signed(data: bytes, sig: bytes):
    exp = hmac.new(SECRET, data, hashlib.sha256).digest()
    if not hmac.compare_digest(exp, sig):
        raise ValueError("Signature invalide (fichier altéré ou clé incorrecte)")
    return pickle.loads(data)

# Exemple d'usage
payload = {"version": 1, "items": [1,2,3]}
data, sig = dumps_signed(payload)

obj = loads_signed(data, sig)  # ok

🔐 Cela garantit l’intégrité et l’authenticité, pas la confidentialité.
Pour chiffrer, combinez HMAC + chiffrement (ex. via cryptography.fernet).


9) Performance : quelques repères

  • Toujours protocol=pickle.HIGHEST_PROTOCOL.
  • Évitez de pickler des objets qui référencent des ressources système (sockets, handles, threads).
  • Pour des grands tableaux numériques, préférez des formats adaptés (NumPy .npy/.npz, Parquet, Feather).
  • Si vous sérialisez très souvent, regroupez/compressez par batch (moins d’IO).
  • Pour du multiprocessing, pickle est utilisé en coulisse ; gardez les fonctions et classes au niveau module.

10) Alternatives à connaître

  • JSON : interopérable, lisible, limité aux types simples.
  • CSV : tabulaire, universel, pas d’imbrications complexes.
  • Parquet/Feather : colonnaire, rapide, idéal pour data frames (via pandas/pyarrow).
  • joblib : pratique pour objets scientifiques (sklearn), supporte la mémoire mappée et la compression.
  • cloudpickle/dill : sérialisent plus de choses (fonctions lambda, closures), utile pour Spark/Dask, mais mêmes avertissements sécurité.

11) Check-list “avant prod”

  • Ai-je vraiment besoin de Pickle (pas JSON/Parquet) ?
  • Les classes sont au niveau module et importables à l’unpickling ?
  • J’utilise protocol=pickle.HIGHEST_PROTOCOL ?
  • Le schéma est versionné (version + migrations testées) ?
  • J’applique une signature HMAC (au minimum) pour détecter l’altération ?
  • Les données sensibles sont chiffrées si nécessaire ?
  • Je n’ouvre jamais des pickles non fiables ?

FAQ

Pickle est-il lisible par d’autres langages ?
Non. C’est un format Python-centré. Pour l’échange, utilisez JSON/Parquet.

Puis-je pickler des objets NumPy/pandas ?
Oui, mais pour l’efficacité et la portabilité, préférez les formats natifs (.npy/.npz, Parquet).

Mes pickles Python 3.12 seront-ils lisibles en 3.8 ?
Souvent oui si vous restez sur des types simples, mais ce n’est pas garanti. Testez et documentez les versions minimales.

Comment diagnostiquer un pickle cassé ?

  • Vérifiez que les modules/classes existent (mêmes noms d’import).
  • Tentez une lecture dans le même environnement qui l’a produit.
  • Ajoutez du versioning et des migrations pour accompagner l’évolution.

12) Mini-recette complète (robuste)

import gzip, hmac, hashlib, pickle
from typing import Any

SECRET = b"...votre-cle-secrete..."  # gérez-la comme un secret applicatif

def dump_secure(obj: Any, path: str, version: int) -> None:
    payload = {"version": version, "data": obj}
    raw = pickle.dumps(payload, protocol=pickle.HIGHEST_PROTOCOL)
    sig = hmac.new(SECRET, raw, hashlib.sha256).digest()
    with gzip.open(path, "wb") as f:
        f.write(len(sig).to_bytes(2, "big") + sig + raw)

def load_secure(path: str) -> dict:
    with gzip.open(path, "rb") as f:
        sig_len = int.from_bytes(f.read(2), "big")
        sig = f.read(sig_len)
        raw = f.read()
    exp = hmac.new(SECRET, raw, hashlib.sha256).digest()
    if not hmac.compare_digest(exp, sig):
        raise ValueError("Signature invalide")
    return pickle.loads(raw)  # -> {"version": X, "data": ...}

Cette recette combine compression, intégrité HMAC et versioning minimal, ce qui couvre 95 % des usages professionnels de Pickle tout en limitant les risques.


Cas pratique — Pickle en cabinet comptable (contexte français, humain et concret)

Cabinet Lemoine & Associés, à Nantes, suit 120 TPE/PME (boulangeries, artisans du bâtiment, e-commerce). Chaque fin de mois, l’équipe récupère :

  • relevés bancaires (CSV),
  • journaux de ventes (CSV/Excel),
  • exports de caisses (Z) et paiements CB.

Le goulot : à chaque itération, les mêmes traitements lourds (nettoyage, typage, jointures) sont refaits, alors que 90 % des données n’ont pas changé. Objectif : accélérer la clôture sans dégrader la traçabilité (audit) ni enfreindre le RGPD.


Solution retenue

  1. Construire un pipeline Python/pandas pour nettoyer et assembler les pièces.
  2. Mémoriser en Pickle les étapes intermédiaires (dataframes prêts à l’analyse) signés HMAC pour l’intégrité.
  3. Versionner les schémas (champ version) afin de gérer les évolutions.
  4. Limiter les données sensibles dans les pickles (pseudonymiser ce qui peut l’être).
  5. Exporter les livrables finaux en Parquet/Excel pour les clients et l’archivage.

Architecture (simple et robuste)

/dossiers_clients/
  C001_BoulangerieRivière/
    source/
      banque_2025-08.csv
      ventes_2025-08.xlsx
    cache/
      ETP_clean_v2.pkl.gz     # Pickle compressé et signé
      journal_v2.pkl.gz
    livrables/
      balance_agee_2025-08.xlsx
      ventes_parquet_2025-08.parquet
  • Le cache contient des objets Python (DataFrame) picklés après nettoyage lourd.
  • Un HMAC (SHA-256) garantit l’intégrité et l’authenticité des pickles.
  • Un hash des sources (checksum) permet d’invalider automatiquement le cache si un fichier source change.

Code clé (extrait commenté)

1) Intégrité & signature des pickles

import gzip, hmac, hashlib, pickle
from pathlib import Path
from typing import Any

SECRET = b"cle-secrete-a-stocker-hors-git"  # .env / vault

def dump_secure(obj: Any, path: Path, version: int) -> None:
    payload = {"version": version, "data": obj}
    raw = pickle.dumps(payload, protocol=pickle.HIGHEST_PROTOCOL)
    sig = hmac.new(SECRET, raw, hashlib.sha256).digest()
    path.parent.mkdir(parents=True, exist_ok=True)
    with gzip.open(path, "wb") as f:
        f.write(len(sig).to_bytes(2, "big") + sig + raw)

def load_secure(path: Path) -> dict:
    with gzip.open(path, "rb") as f:
        sig_len = int.from_bytes(f.read(2), "big")
        sig = f.read(sig_len)
        raw = f.read()
    exp = hmac.new(SECRET, raw, hashlib.sha256).digest()
    if not hmac.compare_digest(exp, sig):
        raise ValueError("Signature invalide (fichier altéré ?)")
    return pickle.loads(raw)

2) Détection de changement des sources (invalidateur de cache)

def checksum_files(*paths: Path) -> str:
    h = hashlib.sha256()
    for p in paths:
        h.update(p.read_bytes())
    return h.hexdigest()[:16]  # court mais suffisant pour invalider

def cache_path_for(client_dir: Path, name: str, version: int, sig: str) -> Path:
    return client_dir / "cache" / f"{name}_v{version}_{sig}.pkl.gz"

3) Pipeline par client (ex. nettoyage banque + ventes)

import pandas as pd

SCHEMA_VERSION = 2

def load_sources(client_dir: Path):
    src = client_dir / "source"
    banque = pd.read_csv(src / "banque_2025-08.csv")
    ventes = pd.read_excel(src / "ventes_2025-08.xlsx")
    return banque, ventes

def clean_pipeline(banque: pd.DataFrame, ventes: pd.DataFrame):
    # Typages & normalisations (exemples)
    banque["date"] = pd.to_datetime(banque["date"], dayfirst=True, errors="coerce")
    ventes["date_facture"] = pd.to_datetime(ventes["date_facture"], dayfirst=True, errors="coerce")
    ventes["montant_ttc"] = ventes["montant_ttc"].astype("float64")

    # Pseudonymisation légère des emails clients pour le cache (RGPD)
    if "email" in ventes.columns:
        ventes["email_hash"] = ventes["email"].fillna("").map(
            lambda s: hashlib.sha256(s.encode()).hexdigest()[:10]
        )
        ventes = ventes.drop(columns=["email"])  # on évite de stocker l'email en clair dans le pickle

    # Jointure illustrative
    ventes["mois"] = ventes["date_facture"].dt.to_period("M")
    kpis = ventes.groupby("mois", as_index=False)["montant_ttc"].sum().rename(columns={"montant_ttc":"CA_mensuel"})
    return {"banque_clean": banque, "ventes_clean": ventes, "kpis": kpis}

4) Orchestrateur (avec cache Pickle)

def run_for_client(client_dir: Path):
    banque, ventes = load_sources(client_dir)
    sig = checksum_files(client_dir/"source/banque_2025-08.csv",
                         client_dir/"source/ventes_2025-08.xlsx")
    pkl = cache_path_for(client_dir, "journal", SCHEMA_VERSION, sig)

    if pkl.exists():
        payload = load_secure(pkl)
        if payload["version"] == SCHEMA_VERSION:
            print("→ Cache chargé :", pkl.name)
            return payload["data"]
        # sinon, on recalcule (migration possible)

    data = clean_pipeline(banque, ventes)
    dump_secure(data, pkl, SCHEMA_VERSION)
    print("→ Cache écrit :", pkl.name)
    return data

5) Livrables (Parquet/Excel)

def export_livrables(client_dir: Path, data: dict):
    liv = client_dir / "livrables"
    liv.mkdir(exist_ok=True, parents=True)

    # Exports stables inter-outils
    data["kpis"].to_parquet(liv / "ventes_kpis_2025-08.parquet", index=False)
    with pd.ExcelWriter(liv / "balance_agee_2025-08.xlsx", engine="xlsxwriter") as xw:
        data["ventes_clean"].to_excel(xw, sheet_name="Ventes", index=False)
        data["banque_clean"].to_excel(xw, sheet_name="Banque", index=False)

Résultat observé (après 1 mois)

  • Temps de traitement par client (lot mensuel) : ~18 min → 2–4 min (-75 à -90 %).
  • Moins d’erreurs de manipulation : mêmes transformations, mêmes versions.
  • Audit trail : chaque livrable peut être régénéré à l’identique avec le cache signé (ou depuis sources si invalidé).

Procédure d’exploitation (équipe)

  1. Déposer les sources dans source/.
  2. Lancer run_for_client() (script batch par portefeuille).
  3. Vérifier le journal des caches (chargé vs recalculé).
  4. Produire les livrables Parquet/Excel et déposer sur SharePoint (lecture seule).
  5. Garder les pickles 3 mois max (politique de rétention) puis purge automatique.

RGPD & sécurité (focus cabinet FR)

  • Ne stockez pas d’IBAN complets, emails, adresses dans les pickles de travail ; privilégiez des hashes (comme ci-dessus) ou remplacez par des identifiants internes.
  • Les pickles sont signés (HMAC) pour l’intégrité, mais non chiffrés : si besoin de confidentialité au repos → chiffrez le dossier de cache (BitLocker/FileVault) ou utilisez un chiffrement applicatif (Fernet).
  • Ne jamais ouvrir un pickle externe/non-fiable.
  • Documentez dans votre registre de traitements (CNIL) : finalité, base légale, durée de conservation, sécurité.

Variante « PME e-commerce »

Même approche pour une boutique Shopify/Prestashop : pickler le catalogue enrichi (produits + mapping TVA + familles), recalculer uniquement si l’export produits change, et publier les KPIs en Parquet pour Power BI.


Pièges & parades

  • Classe introuvable à l’ouverture → définir les classes au niveau module, garder le même nom de module.
  • Schéma qui évolue → ajouter version + fonctions de migration.
  • Pickles trop volumineux → compresser (gzip), ou préférer Parquet pour les DataFrames denses.

Mini-checklist avant adoption

  • J’ai un invalidateur de cache basé sur un hash des sources.
  • Mes pickles sont signés (HMAC) et compressés.
  • J’ai un champ version et une procédure de migration.
  • Les données sensibles sont exclues ou pseudonymisées.
  • Les livrables sont exportés en formats ouverts (Parquet/Excel).

Pickle est un outil de persistance interne extrêmement pratique pour Python : rapide, fidèle et simple. Utilisez-le en conscience : jamais sur des sources non fiables, toujours avec versioning et, idéalement, signe/ chiffrez vos fichiers. Pour l’échange inter-outils et le long terme, basculez vers des formats ouverts (JSON/Parquet).


Téléchargez simplement Python, et vous aurez pickle :

  • Télécharger Python (officiel) : page des téléchargements sur python.org. (Python.org)
    (Dernière version indiquée : Python 3.13.7.) (Python.org)
  • Documentation pickle : guide officiel (EN) et version FR. (Python documentation)

Option “tout-en-un” pour data (inclut Python et l’écosystème scientifique) :

  • Anaconda Distribution (Windows/macOS/Linux). (Anaconda)

Recommandés

CSV en Python — du téléchargement au...
Le CSV paraît simple, jusqu’au...
En savoir plus
Python : pandas.to_csv — Exporter propre, fiable...
DataFrame.to_csv devient un contrat d’échange :...
En savoir plus
Python : pandas.read_csv — Guide pratique enrichi
Pourquoi read_csv reste incontournableParce qu’il...
En savoir plus
Pratique de l’apprentissage automatique avec scikit-learn et...
Cet article vous guide, pas à...
En savoir plus
Python & finance PME — un kit...
Pour une PME, la finance est...
En savoir plus
Python pour la finance — les fonctions de base
Python pour la finance — les fonctions...
Python est devenu l’outil “couteau suisse”...
En savoir plus

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

error: Content is protected !!