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
versionau 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. viacryptography.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,
pickleest 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
- Construire un pipeline Python/pandas pour nettoyer et assembler les pièces.
- Mémoriser en Pickle les étapes intermédiaires (dataframes prêts à l’analyse) signés HMAC pour l’intégrité.
- Versionner les schémas (champ
version) afin de gérer les évolutions. - Limiter les données sensibles dans les pickles (pseudonymiser ce qui peut l’être).
- 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)
- Déposer les sources dans
source/. - Lancer
run_for_client()(script batch par portefeuille). - Vérifier le journal des caches (chargé vs recalculé).
- Produire les livrables Parquet/Excel et déposer sur SharePoint (lecture seule).
- 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
versionet 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)




