Opérations sur les fichiers en Python

Opérations sur les fichiers en Python

Cet article explique les opérations sur les fichiers en Python.

En gardant à l'esprit la « sécurité », « l'efficacité » et la « lisibilité », ce guide explique les concepts étape par étape, des bases aux techniques pratiques.

YouTube Video

Opérations sur les fichiers en Python

La manipulation des fichiers est une compétence fondamentale essentielle, des petits scripts aux applications à grande échelle.

Ouverture et fermeture de fichiers

Voyons d'abord des exemples de lecture et d'écriture de fichiers texte. L'utilisation de l'instruction with (gestionnaire de contexte) garantit la fermeture correcte du fichier.

Le code suivant ouvre un fichier texte, lit son contenu et le traite ligne par ligne.

1# Open and read a text file safely using a context manager.
2with open("example.txt", "r", encoding="utf-8") as f:
3    # Read all lines lazily (as an iterator) and process each line.
4    for line in f:
5        print(line.rstrip())
  • Spécifier explicitement encoding="utf-8" permet de réduire les problèmes liés aux différences de plateforme.
  • Même avec de gros fichiers, for line in f est économe en mémoire.

Écriture (écrasement et ajout)

Faites attention au mode lorsque vous écrivez dans des fichiers. w permet d'écraser, a permet d'ajouter à la fin du fichier. Utilisez également l'instruction with lors de l'écriture.

Le code suivant montre des exemples de base d'écrasement et d'ajout.

1# Write (overwrite) to a file.
2with open("output.txt", "w", encoding="utf-8") as f:
3    f.write("First line\n")
4    f.write("Second line\n")
5
6# Append to the same file.
7with open("output.txt", "a", encoding="utf-8") as f:
8    f.write("Appended line\n")
  • Ce code écrit du texte dans output.txt, puis ajoute du texte supplémentaire dans le même fichier. Le mode "w" écrase le fichier, tandis que le mode "a" ajoute une nouvelle ligne à la fin du contenu existant.
  • Si vous devez forcer l'écriture, appelez flush(), mais en général, le contenu est vidé automatiquement à la fin du contexte.
  • Si plusieurs processus ou threads peuvent écrire en même temps, vous devez envisager un contrôle exclusif, comme le verrouillage de fichier.

Lecture et écriture de données binaires

Les images et fichiers compressés sont manipulés en mode binaire (rb ou wb). Contrairement au mode texte, encoding est ignoré.

Le code suivant lit un fichier binaire et le copie dans un autre fichier en mode binaire.

1# Read and write binary files.
2with open("image.png", "rb") as src:
3    data = src.read()
4
5with open("copy.png", "wb") as dst:
6    dst.write(data)
  • Pour les fichiers binaires volumineux, évitez de tout lire d'un coup avec read(). Lire et écrire par portions consomme moins de mémoire.

Exemple de manipulation de gros fichiers par portions

Pour les fichiers très volumineux qui ne tiennent pas en mémoire, lisez et écrivez-les par morceaux de taille fixe. Puisque l'opération dépend de l'I/O, ajuster la taille du tampon est utile.

Le code suivant copie un fichier par blocs de 64 Ko. Cela permet une opération rapide tout en gardant une faible consommation de mémoire.

1# Copy a large file in chunks to avoid using too much memory.
2CHUNK_SIZE = 64 * 1024  # 64 KB
3
4with open("large_source.bin", "rb") as src,      open("large_dest.bin", "wb") as dst:
5    while True:
6        chunk = src.read(CHUNK_SIZE)
7        if not chunk:
8            break
9        dst.write(chunk)
  • Vous pouvez ajuster la taille des blocs selon les caractéristiques de votre réseau ou disque. Sur les SSD, une taille de bloc légèrement supérieure fonctionne souvent mieux.

Exemple utilisant l'argument buffering

En spécifiant l'argument buffering dans la fonction open(), vous pouvez contrôler la taille du tampon utilisée en interne par Python. Cela vous permet d'optimiser davantage l'efficacité des entrées/sorties.

 1# Explicitly set the internal buffer size to 128 KB for faster I/O.
 2CHUNK_SIZE = 64 * 1024
 3BUFFER_SIZE = 128 * 1024  # 128 KB internal buffer
 4
 5with open("large_source.bin", "rb", buffering=BUFFER_SIZE) as src,      open("large_dest.bin", "wb", buffering=BUFFER_SIZE) as dst:
 6    while True:
 7        chunk = src.read(CHUNK_SIZE)
 8        if not chunk:
 9            break
10        dst.write(chunk)
  • Si vous définissez la valeur de buffering à 0, les E/S sont effectuées sans mise en tampon ; 1 active la mise en tampon par ligne, tandis que les valeurs de 2 ou plus utilisent un tampon de la taille spécifiée en octets.
  • En général, la valeur par défaut est suffisante car le système d'exploitation gère efficacement la mise en cache, mais ajuster ce paramètre peut être efficace pour des fichiers très volumineux ou des périphériques particuliers.

Opérations modernes sur les fichiers avec Pathlib

Le module standard pathlib rend la gestion des chemins plus intuitive. Cela améliore la lisibilité et la sécurité par rapport aux chemins sous forme de chaînes.

 1from pathlib import Path
 2
 3path = Path("data") / "notes.txt"
 4
 5# Ensure parent directory exists.
 6path.parent.mkdir(parents=True, exist_ok=True)
 7
 8# Write and read using pathlib.
 9path.write_text("Example content\n", encoding="utf-8")
10content = path.read_text(encoding="utf-8")
11print(content)
  • Ce code montre comment créer un dossier avec pathlib, écrire dans un fichier texte et en lire le contenu. Avec un objet Path, vous pouvez gérer les chemins de manière intuitive et sécurisée.
  • Path dispose d'API pratiques, telles que iterdir() et glob(). Vous pouvez écrire du code sans vous soucier des séparateurs de chemin selon le système d'exploitation.

Fichiers et dossiers temporaires (tempfile)

On peut créer des fichiers temporaires en toute sécurité avec tempfile. Cela permet d'éviter les conditions de course de sécurité et les collisions de noms.

Le code suivant montre un exemple de création de données temporaires avec un fichier temporaire.

 1import tempfile
 2
 3# Create a temporary file that is deleted on close.
 4with tempfile.NamedTemporaryFile(
 5    mode="w+",
 6    encoding="utf-8",
 7    delete=True
 8) as tmp:
 9    tmp.write("temporary data\n")
10    tmp.seek(0)
11    print(tmp.read())
12
13# tmp is deleted here
  • Ce code crée un fichier temporaire, y écrit puis lit des données, et le supprime automatiquement à la fin du bloc with. En utilisant tempfile.NamedTemporaryFile, vous pouvez gérer les fichiers temporaires en toute sécurité et sans conflit. Comme delete=True est spécifié, le fichier est supprimé automatiquement.
  • Sous Windows, il se peut que vous ne puissiez pas rouvrir le fichier immédiatement depuis un autre handle. Dans ce cas, réglez delete=False et gérez la suppression manuellement.

shutil : opérations de haut niveau pour copier, déplacer et supprimer

La copie, le déplacement et la suppression récursive de fichiers et de répertoires sont facilités avec shutil.

 1import shutil
 2
 3# Copy a file preserving metadata.
 4shutil.copy2("source.txt", "dest.txt")
 5
 6# Move a file or directory.
 7shutil.move("old_location", "new_location")
 8
 9# Remove an entire directory tree (use with caution).
10shutil.rmtree("some_directory")
  • shutil.copy2 copie également les métadonnées (comme la date de modification). move essaiera de déplacer le fichier même si le renommage n'est pas possible.
  • rmtree est une opération dangereuse : vérifiez toujours et faites une sauvegarde avant de supprimer.

Métadonnées de fichiers (os.stat) et gestion des permissions

La taille, la date de modification et les permissions d'un fichier peuvent être lues et modifiées avec os et stat.

Le code suivant récupère les informations de base via os.stat et modifie les permissions avec os.chmod.

 1import os
 2import stat
 3from datetime import datetime
 4
 5st = os.stat("example.txt")
 6print("size:", st.st_size)
 7print("modified:", datetime.fromtimestamp(st.st_mtime))
 8
 9# Make file read-only for owner.
10os.chmod("example.txt", stat.S_IREAD)
  • Les permissions fonctionnent différemment sous les systèmes POSIX et Windows. Si la compatibilité multi-plateforme est importante, utilisez des API de haut niveau ou gérez les cas particuliers.

Verrouillage de fichiers (contrôle exclusif) — différences entre Unix et Windows

Un contrôle exclusif est nécessaire si plusieurs processus accèdent au même fichier en parallèle. UNIX utilise fcntl, tandis que Windows utilise msvcrt.

Le code suivant utilise fcntl.flock sur les systèmes UNIX pour obtenir un verrou exclusif lors de l'écriture.

 1# Unix-style file locking example
 2import fcntl
 3
 4with open("output.txt", "a+", encoding="utf-8") as f:
 5    fcntl.flock(f, fcntl.LOCK_EX)
 6    try:
 7        f.write("Safe write\n")
 8        f.flush()
 9    finally:
10        fcntl.flock(f, fcntl.LOCK_UN)
  • Ce code obtient un verrou exclusif avec fcntl.flock sur les systèmes de type UNIX, afin d'écrire en sécurité et d'éviter les écritures simultanées. Libérez toujours le verrou après le traitement pour permettre l'accès aux autres processus.
  • Sous Windows, utilisez msvcrt.locking(). Pour une utilisation haut niveau, envisagez des bibliothèques externes comme portalocker ou filelock.

Modèles d'écriture atomique de fichiers

Pour éviter la corruption de fichier lors des mises à jour, écrivez dans un fichier temporaire puis remplacez-le avec os.replace en cas de succès.

Écrire dans un fichier temporaire puis le remplacer permet d'éviter la corruption en cas de plantage.

 1import os
 2from pathlib import Path
 3import tempfile
 4
 5def atomic_write(path: Path, data: str, encoding="utf-8"):
 6    # Write to a temp file in the same directory and atomically replace.
 7    dirpath = path.parent
 8    with tempfile.NamedTemporaryFile(
 9        mode="w",
10        encoding=encoding,
11        dir=str(dirpath),
12        delete=False
13    ) as tmp:
14        tmp.write(data)
15        tmp.flush()
16        tempname = tmp.name
17
18    # Atomic replacement (on most OSes)
19    os.replace(tempname, str(path))
20
21# Usage
22atomic_write(Path("config.json"), '{"key":"value"}\n')

os.replace effectue un remplacement atomique à l'intérieur du même système de fichiers. Notez que l'atomicité n'est pas garantie entre différents montages.

Accès rapide avec mmap (pour les données volumineuses)

Pour un accès aléatoire à de gros fichiers, mmap améliore les performances d'entrée/sortie. Cela concerne principalement des opérations binaires.

Le code suivant effectue un mapping mémoire d'un fichier et lit ou écrit des plages d'octets spécifiques. Soyez prudent lors du changement de la taille du fichier.

1import mmap
2
3with open("data.bin", "r+b") as f:
4    mm = mmap.mmap(f.fileno(), 0)  # map entire file
5    try:
6        print(mm[:20])  # first 20 bytes
7        mm[0:4] = b"\x00\x01\x02\x03"  # modify bytes
8    finally:
9        mm.close()
  • Ce code mappe un fichier binaire en mémoire avec mmap et effectue des lectures/écritures directes au niveau des octets. Le mappage mémoire permet un accès rapide et aléatoire aux grands ensembles de données.
  • mmap est efficace, mais une mauvaise utilisation peut entraîner des problèmes de cohérence des données. Appelez flush() pour synchroniser si besoin.

CSV / JSON / Pickle : lecture et écriture par format

Certains formats de données disposent de modules dédiés. Utilisez csv pour CSV, json pour JSON et pickle pour sauvegarder des objets Python.

Le code suivant propose des exemples de base pour la lecture et l'écriture de CSV et JSON, ainsi que l'utilisation de Pickle. Comme Pickle peut exécuter du code arbitraire, évitez de charger des données provenant de sources non fiables.

 1import csv
 2import json
 3import pickle
 4
 5# CSV write
 6with open("rows.csv", "w", encoding="utf-8", newline="") as f:
 7    writer = csv.writer(f)
 8    writer.writerow(["name", "age"])
 9    writer.writerow(["Alice", 30])
10
11# JSON write
12data = {"items": [1, 2, 3]}
13with open("data.json", "w", encoding="utf-8") as f:
14    json.dump(data, f, ensure_ascii=False, indent=2)
15
16# Pickle write (only for trusted environments)
17obj = {"key": "value"}
18with open("obj.pkl", "wb") as f:
19    pickle.dump(obj, f)
  • Il est recommandé de spécifier newline="" pour CSV afin d'éviter les lignes vides supplémentaires sous Windows. Avec ensure_ascii=False, les caractères UTF-8 restent lisibles dans le JSON.

Lecture et écriture directe de fichiers compressés (gzip / bz2 / zipfile)

Gérer directement gzip et zip permet d'économiser de l'espace disque. La bibliothèque standard inclut les modules correspondants.

Le code ci-dessous est un exemple simple de lecture et d'écriture de fichiers gzip en mode texte.

1import gzip
2
3# Write gzipped text.
4with gzip.open("text.gz", "wt", encoding="utf-8") as f:
5    f.write("Compressed content\n")
6
7# Read gzipped text.
8with gzip.open("text.gz", "rt", encoding="utf-8") as f:
9    print(f.read())
  • Il existe un compromis entre le taux de compression et la vitesse en fonction du niveau et du format de compression.

Mesures de sécurité et de vulnérabilité

Les points suivants peuvent être pris en compte pour les mesures de sécurité et de vulnérabilité.

  • N’utilisez jamais de données non fiables directement dans les noms de fichiers ou chemins.
  • Utilisez Pickle uniquement avec des sources fiables.
  • Minimisez les privilèges d’exécution et n’accordez que les permissions minimales aux processus manipulant les fichiers.
  • Utilisez des fichiers temporaires avec tempfile et ne stockez pas de fichiers non chiffrés dans des répertoires publics.

Si vous utilisez une entrée utilisateur dans les chemins de fichiers, la normalisation et la validation sont nécessaires. Par exemple, utilisez Path.resolve() et vérifiez les répertoires parents.

 1from pathlib import Path
 2
 3def safe_path(base_dir: Path, user_path: str) -> Path:
 4    candidate = (base_dir / user_path).resolve()
 5    if base_dir.resolve() not in candidate.parents and base_dir.resolve() != candidate:
 6        raise ValueError("Invalid path")
 7    return candidate
 8
 9# Usage
10base = Path("/srv/app/data")
11
12# Raises an exception: attempted path traversal outside `base`
13safe = safe_path(base, '../etc/passwd')
  • Soyez particulièrement vigilant lors de l’utilisation d’entrées externes comme chemins de fichiers dans les applications web ou API publiques.

Résumé des modèles courants

  • Utilisez toujours l’instruction with (fermeture automatique).
  • Spécifiez explicitement encoding pour les données textuelles.
  • Lisez et écrivez les gros fichiers par portions.
  • Implémentez le verrouillage des fichiers pour les ressources partagées.
  • Pour les mises à jour critiques, utilisez le modèle atomique « écriture dans un fichier temporaire → os.replace ».
  • Confirmez toujours et créez des sauvegardes avant d’effectuer des opérations dangereuses (comme la suppression ou l’écrasement).
  • Normalisez et validez lors de l’utilisation d’entrées externes comme chemins de fichiers.

Résumé

Pour les opérations sur les fichiers, il est important d’utiliser des techniques sûres et fiables comme l’utilisation des blocs with, la spécification explicite de l’encodage et l’écriture atomique. Pour le traitement à grande échelle ou l’accès en parallèle, il est nécessaire de mettre en place des systèmes de verrouillage et de gestion des journaux afin d’éviter la corruption et les conflits de données. L'équilibre entre efficacité et sécurité est la clé pour des opérations de fichiers fiables.

Vous pouvez suivre l'article ci-dessus avec Visual Studio Code sur notre chaîne YouTube. Veuillez également consulter la chaîne YouTube.

YouTube Video