Le traitement de fichiers avec Python

Vous avez déjà des rudiments de Python et vous souhaiteriez pouvoir manipuler des fichiers avec ce langage ? Suivez le guide !

a marqué ce sujet comme résolu.

Tout le monde se secoue ! :D

J’ai commencé (vendredi 06 janvier 2023 à 13h29) la rédaction d’un tutoriel au doux nom de « Le traitement de fichiers avec Python » et j’ai pour objectif de proposer en validation un texte aux petits oignons. Je fais donc appel à votre bonté sans limites pour dénicher le moindre pépin, que ce soit à propos du fond ou de la forme. Surtout, remettez-moi dans le droit chemin si je raconte des bêtises ou si je suis à côté de la plaque ! ^^

Vous pourrez consulter la bêta à votre guise à l’adresse suivante :

Merci !

+0 -0

Salut,

Pas mal ce retravail de ton tutoriel, je trouve que c’est beaucoup mieux cadré !

Mémoires et fichiers

Au niveau de la mémoire en parlant des différents types, est-ce que tu penses aussi parler de mémoire cache ? Ensuite je ne sais pas si « disque dur » est le terme le plus adapté aujourd’hui pour parler de la mémoire persistante avec les SSD un peu partout.

Je ne suis pas sûr qu’il soit intéressant de voir la mémoire comme un tableau de bits plutôt qu’un tableau d’octets. Y a-t-il vraiment des mémoires utilisées aujourd’hui que l’on peut manipuler aussi finement ? Je dirais que les abstractions qui permettent de visualiser la mémoire comme des cases (parce que ce n’est alors plus une considération physique) le font plutôt avec des tableaux d’octets.

J’ai du mal à comprendre la transition que tu fais entre les octets et les fichiers et je trouve bizarre de parler d’un répertoire qui couvrirait l’intégralité du disque dur, ça ne me semble pas correspondre à la réalité des systèmes de fichiers. Tu n’en parles d’ailleurs pas du tout, pourtant ceux sont eux qui permettent de faire la liaison entre fichiers comme ensembles d’octets et octets stockés sur un disque.

En lisant tes explications on pourrait croire que les répertoires sont des espèces de partitions du disque. Pourtant du point de vue du disque les répertoires n’existent même pas. C’est le système de fichiers qui stocke des blocs de données particuliers sur le disque pour donner forme à une telle hiérarchie totalement abstraite.

À la suite de ton croquis représentant l’arborescence du système tu parles d’éléments en rouge représentant l’adresse d’un fichier particulier. Je n’ai pas vu de rogue sur le croquis.

Tu dis qu’un chemin relatif ne permet pas d’accéder à tous les fichiers, j’aimerais bien plus de précisions là-dessus, pourquoi ne pourrait-on pas remonter jusqu’à un répertoire parent avec .. pour ensuite redescendre où l’on veut ?
Ça fait aussi justement qu’un chemin relatif peut être plus long qu’un absolu : si je veux pointer le fichier /etc/fstab depuis mon répertoire /home/entwanne je dois utiliser pour chemin relatif ../../etc/fstab.

Le module pathlib

Je trouve que ça manque un peu d’exemples d’utilisation, et notamment de ce qui est vraiment utilise dans ce module : la formation de chemins avec l’opérateurs /, les méthodes utiles pour manipuler des chemins concrets (open, read_text, etc.).

Difficile en l’état de comprendre le but de ce chapitre.

Le module os

Pour résoudre le problème évoqué dans la première section je pense qu’il serait plus judicieux de recevoir un chemin en argument du programme et de l’utiliser comme base pour les opérations qui suivent plutôt que de changer de répertoire courant.
Le changement de répertoire pourrait avoir des effets indésirables (notamment sur l’import de modules Python).

Tu dis que la fonction os.rmdir supprime un répertoire et son contenu, pourtant il me semble qu’elle lève une exception quand le répertoire n’est pas vide.

À propos de os.rename tu devrais parler des limitations de la fonction (impossible de déplacer un fichier vers une autre partition) et peut-être introduire le module shutil.

Les opérations que tu présentes sur les chemins dans ce chapitre mériteraient d’être aussi étudiées dans le chapitre concernant pathlib.

Les fichiers textuels

Dans la première section tu dis que les fichier texte sont encodés en UTF-8 (ce n’est pas toujours le cas) et que chaque caractère est encodé sur un octet (ce n’est souvent pas le cas), tu devrais vérifier tes informations. Pour ce qui est du \n il correspond bien à un seul caractère (l’entrée 0x0A dans la table ASCII) codé sur un octet dans les encodages basés sur ASCII. \n n’est qu’une représentation de ce caractère.

Dans la deuxième section je ne comprends pas ta phrase « il faut absolument le fermer, sinon on ne pourra plus accéder au fichier via Python ».

Pourquoi as-tu deux foisfile.close() dans ton exemple sur le try/finally ? Le bloc finally est exécuté dans tous les cas, qu’une exception soit levée ou non, il n’est donc pas nécessaire de fermer le fichier dans le bloc try puisqu’il sle sera dans le finally.

De même, pourquoi mets-tu un file.close() dans le bloc with alors que c’est justement l’intérêt de ce bloc que de ferme le fichier par lui-même ? Sur quoi te bases-tu pour dire qu’il « este préférable de terminer le bloc par l’instruction file.close() » ?

Dans la troisième section tu dis qu’un fichier ne pourrait pas être ouvert à la fois en lecture et écriture, c’est pourtant permis par exemple par le mode r+ et d’autres. Tu oublies aussi de préciser que le mode w tronque le contenu du fichier, tu expliques même l’inverse dans la section « Écrire dans un fichier » où tu présentes un exemple où le contenu ne serait pas tronqué.

Le mode x permet parfaitement d’écrire dans un fichier, il assure juste que le fichier n’existait pas avant.

Les fichiers ne possèdent pas de méthode trunc mais une méthode truncate, as-tu essayé les exemples que tu présentes ?

Ton exemple de fichier JSON est invalide, il manque des virgules par endroits.

Pour ce qui est des modules csv et json je pense que tu devrais plutôt donner en lien les pages de doc de ces modules qu’un site bourré de publicités.

Pourquoi parler de pickle dans un chapitre sur les fichiers texte ? Et pourquoi dis-tu que le module nécessite d’être installé via pip (il fait partie de la bibliothèque standard) ?
Les exemples que tu utilises ensuite ne sont pas bon puisque pickle attend des fichiers et non des chemins.

Le module pandas

Je reste sur ma faim après ce chapitre, au final tu ne présentes pandas que pour lire/écrire des fichiers dans différents formats et bénéficier d’une méthode filter un peu magique.

N’y a-t-il pas d’autres choses utiles dans ce paquet qui mériteraient d’être détaillées ici ?

Bonjour,

Peut-être devrais je présenter la mémoire comme un tableau d’octets dont chaque cases est subdivisée en 8 bits ?

Pour ce qui est des chemins relatifs, je sais que pathlib nous permet d’utiliser l’attribut parent

J’ai bien conscience que je présente une utilisation réduite de pathlib, l’ennui est que je vais par la suite présenter le module os et la fonction open, et ça serait un peu redondant…

Je vais me renseigner plus en détail sur les limites de os.rmdir et os.rename, ainsi que sur le module shutil…

Je vais revoir ma partie sur le stockage de fichiers.

Je suis obligé d’interrompre le message ici, je le reprendrais plus tard.

Bonne journée,

@flopy78

Salut,

Peut-être devrais je présenter la mémoire comme un tableau d’octets dont chaque cases est subdivisée en 8 bits ?

Comme déjà abordé dans cette réponse, je ne comprends pas ce que vient faire cette explication sur la mémoire. Quelle information pertinente pour manipuler des fichiers en Python apporte-t-elle ? Tu parles de registres, de mémoire vive, de disque durs, de bits et d’octets, mais tout ceci est tout de même très éloigné du niveau d’abstraction que Python permet de manipuler. Pour un développeur Python, ce qui compte surtout sont deux choses :

  • les objets manipulés dans nos programmes vivent dans une mémoire volatile (i.e. qui ne retient pas l’information au delà du run du programme) ;
  • Python offre, essentiellement via pathlib et shutil (+ des modules pour lire/écrire des formats divers), des API pour interagir avec le système de fichiers qui permet (notamment) de lire et écrire de l’information stockée de manière durable.

Les registres, on s’en fout complètement quand on code en Python. Le fait que la mémoire soit organisée en octets (même ça, c’est une vision hyper naïve d’ailleurs, il y a plein d’abstractions entre les octets et les données qu’on manipule) et que l’information soit encodée sous forme binaire, on s’en fout aussi pas mal. Ça commence à devenir intéressant pour comprendre les différences entre différents formats de fichiers, mais c’est quelque chose qui vient plus tard dans l’apprentissage. En faire l’introduction ne me semble pas très intéressant.

J’ai bien conscience que je présente une utilisation réduite de pathlib, l’ennui est que je vais par la suite présenter le module os et la fonction open, et ça serait un peu redondant…

Je vais me renseigner plus en détail sur les limites de os.rmdir et os.rename, ainsi que sur le module shutil…

À mon avis, c’est une erreur de parler du modèle os en profondeur. Son API est vieille. pathlib et shutil implémentent en effet beaucoup de choses que os offre déjà, avec le but d’être plus propre et ergonomique. Si tu veux éviter les répétitions, c’est côté os qu’il faut les supprimer et passer plus de temps sur pathlib et shutil. Un programme en Python qui manipule des fichiers a très peu de raisons d’utiliser os aujourd’hui (perso quasiment la seule raison pour laquelle je l’importe est pour utiliser os.PathLike). Il y a quelques opérations assez bas niveau qui ne sont pas reproduites ailleurs, mais elles sont d’utilité plutôt marginale.

Pour résoudre le problème évoqué dans la première section je pense qu’il serait plus judicieux de recevoir un chemin en argument du programme et de l’utiliser comme base pour les opérations qui suivent plutôt que de changer de répertoire courant.

Le changement de répertoire pourrait avoir des effets indésirables (notamment sur l’import de modules Python).

Pour enfoncer un peu plus le clou là-dessus, utiliser os.chdir est presque tout le temps un antipattern. Ça affecte un état global (l’environnement d’exécution) ce qui

  • comme le dit @entwanne peut avoir des conséquences surprenantes pour les imports de modules ;
  • rend l’exécution dépendante de cet état global ce qui peut rapidement rendre le programme difficile à comprendre et donc à maintenir/débugger.

Ce sont deux désavantages assez gros par rapport à explicitement attendre un chemin en entrée et faire les manipulations relativement à ce chemin.

Bonjour,

J’ai apporté quelques corrections à mon tutoriel.

Je vais me renseigner sur les flux "r+"…

Peut-être devrais je présenter pickle à part ? (dans ce cas, ça ferait une toute petite partie…)

Je vais revoir mon usage de os.chdir() et développer la partie sur shutil.

Merci beaucoup !

Bonne journée,

@flopy78

Je viens de lire la partie sur pathlib, et à mon avis tu passes un peu à côté de ce qui est important.

D’une part, on a assez rarement besoin d’écrire des chemins complets à la main, et encore moins en dur dans un script, alors que quasiment tous tes exemples définissent un chemin complet arbitraire. D’autre part, tu présentes des manières assez maladroites de construire des fichiers au lieu d’utiliser la syntaxe la plus courante à coup de /.

Typiquement, un programme comme celui

from pathlib import Path # nous n'utiliserons que la classe Path

cwd = "/Utilisateurs/Georgette/Photos" #notre espace de travail

liste_annees = range(2010,2023) #la borde supérieur est exclue (d'où le 2023 au lieu de 2022)
liste_mois = ["Janvier","Février","Mars","Avril","Mai","Juin","Juillet","Août","Septembre","Octobre","Novembre","Décembre"]

for annee in liste_annees:
    Path(cwd,str(annee)).mkdir()
    for mois in liste_mois:
        Path(cwd,str(annee),mois).mkdir()

pourrait être réécrit :

from pathlib import Path
from typing import Iterable
import calendar

def create_all_dirs(root: Path, years: Iterable[int]):
    for year in map(str, years):
        for month in calendar.month_name[1:]:
	    (root / year / month).mkdir(parents=True)

if __name__ == "__main__":
    create_all_dirs(
        root=Path.home() / "Photos",
	years=range(2020, 2023),
    )

Ce qui est important, c’est surtout de montrer comment le code métier peut simplement prendre un Path quelconque (qui peut être relatif ou absolu, on s’en fout en fait) et travailler avec facilement parce qu’on peut utiliser / pour ajouter des segments, et appeler des méthodes pour manipuler le chemin. Bon là j’en rajoute un peu avec les annotations etc qu’on s’amuserait pas forcément à écrire sur un script en one shot, mais ce qui compte vraiment est surtout l’idée générale d’écrire du code qui est relatif à un Path puis l’appeler en lui fournissant le chemin à partir duquel travailler. Cette idée s’applique aussi à ton blabla sur "gérer le répertoire de travail". Si t’écris ton script avec une variable globale cwd avec une valeur en dur, c’est à peine mieux que d’utiliser os.chdir. Ce qu’il faut faire, c’est la même chose que le code ci-dessus : isoler le service à rendre dans un répertoire quelconque sous forme d’une fonction (ou méthode, peu importe) qui prend le répertoire à traiter en argument. De cette manière, le code que tu écris est agnostique d’un état global (en dehors du système de fichiers lui-même, mais celui-là est inévitable).


Quelques remarques secondaires :

Pour retrouver le fichier, il faut partir du plus grand dossier (qui couvre l’intégralité du disque dur), appelé la racine du système, et passer par les différents dossiers dans lesquels le fichier est inclus, jusqu’au fichier lui-même. Bien sûr, l’analogie est imparfaite, puisqu’une maison est forcément comprise dans un quartier, un pâté de maison, une rue et porte forcément un numéro.

Hmm, chez moi la racine fait 4096 octets, et bien heureusement ça ne suffit pas à recouvrir l’intégralité du disque dur (de toute façon j’ai un SSD et pas un disque dur).

$ ls -ld /
drwxr-xr-x 17 root root 4096 Oct 23 00:22 /

En plus, depuis la racine on peut accéder à des trucs qui sont pas sur le disque dur, comme /dev/tty1. Les systèmes de fichiers abstraient beaucoup de choses, surtout sous les unixoïdes. Donc le lien avec ce qui passe sur le matériel est pas aussi trivial que "le chemin donne l’addresse vers un truc sur le disque".

Par ailleurs, si tu sais que l’analogie est pas bonne, pourquoi s’en servir ? Comprendre une arborescence de fichiers, c’est pas si compliqué que ça. Pas besoin d’une analogie pour s’en sortir donc autant l’éviter si t’en trouves pas une satisfaisante. Bon en plus elle est vraiment pas terrible parce que ça marche pas pour les liens par exemple. L’addresse d’un fichier sur le disque dur, c’est plutôt son inode, sur ext4 du moins (et encore, pas franchement).

Voici le chemin du fichier "paysage.jpg", écrit selon la syntaxe de Mac Os et Linux : "/Utilisateurs/Georgette/Photos/paysage.jpg".

Sous Linux, ce serait plutôt /home/Georgette dans la plupart des cas, mais en fait ça peut être n’importe quoi. Mais de toute façon, Path.home() abstrait ça pour nous.

Les objets de la classe Path peuvent être convertis en chemin sous forme de chaines de caractères via la fonction de conversion str.

Certes, mais en pratique c’est rarement utile parce que Path implémente __str__, et donc print(path) et f"{path}" font ce qu’il faut par défaut.

Par exemple, Path("Utilisateurs","Georgette","Photos","paysage.jpg").relative_to("/Utilisateurs/Georgette") donne "Photos/paysage.jpg".

Non, ça donne une ValueError parce que Path("Utilisateur", ...) est relatif, pas absolu.

Par exemple, Path("Utilisateurs","Georgette","Photos","paysage.jpg").with_name("fleur.png") donnera "/Utilisateurs/Georgette/Photos/fleur.png" (selon la syntaxe de Linux).

Même idée ici, ça va donner Utilisateurs/Georgette/Photos/fleur.png (relatif, pas absolu). Assure-toi de tester tes exemples pour éviter ce genre d’erreurs…

max([file.stat().st_size for file in dir.iterdir()])

C’est un peu dommage de construire une liste si on va pas s’en servir. Si t’enlèves les crochets, ça marche pareil mais sans construire une liste potentiellement énorme au passage (ça utilise un générateur).

Bonjour,

Merci pour toutes ces super infos !!! :D

Je vais intégrer l’opérateur "/", "Path.home()" (qui renvoie le chemin du dossier de l’utilisateur courant, si j’ai bien compris) et je vais revoir mon explication de la racine système… Je vais également mettre à jour ma section sur le répertoire de travail et corriger mes erreurs !

J’attends avec impatience votre prochaine critique…

Bonne journée,

@flopy78

J’ai survolé rapidement suite aux mises à jour et suis tombé sur ceci :

Le premier élément (à gauche du /) doit absolument être une instance de Path !

Je pense que tu devrais vraiment refaire une passe sur les affirmations / exemples que tu donnes pour les vérifier. En l’occurrence ça n’est pas le cas ici.

>>> "a" / pathlib.Path("b")
PosixPath('a/b')

Bonjour,

En effet je suis allé un peu vite dans mon affirmation : il serait sûrement plus juste de dire que au moins un des éléments doit être une instance de Path (autrement, on se retrouve à diviser des chaines de caractères entre elles, et ça ne va pas du tout).

J’ai également avancé sur la partie analyse de données avec pandas (filtrage, nettoyage, corrélation, graphiques…) et vais certainement bientôt refaire une mise à jour.

Je souhaiterais aussi mentionner la méthode Path.rglob, ce qui m’évitera d’employer des expressions régulières telles que "*/" avec Path.glob (ce qui n’est pas faux, mais plus difficile à comprendre que Path.rglob("*"), car la regex est plus complexe).

Bonne journée,

@flopy78

Je souhaiterais aussi mentionner la méthode Path.rglob, ce qui m’évitera d’employer des expressions régulières telles que "*/" avec Path.glob (ce qui n’est pas faux, mais plus difficile à comprendre que Path.rglob("*"), car la regex est plus complexe).

flopy78

En passant, glob n’accepte pas une regex mais un pattern fnmatch (plus **/). C’est précisé dans la documentation, et je pense que tu devrais la lire plus attentivement. Il y a beaucoup de petites erreurs le long de ton tuto que tu aurais pu éviter simplement en lisant la documentation. Par exemple, c:/ sous Windows n’est pas une racine mais une ancre. La racine est un backslash, comme montré dans cet exemple de la doc :

>>> PureWindowsPath('c:/Program Files/').root
'\\'

Bref, lis bien toute la documentation des choses que tu présentes, en plus de t’assurer que tes exemples fonctionnent bien comme tu le dis.

Bonjour,

J’ai corrigé ma confusion entre regex et fnmatch, supprimé la partie sur os, poursuivi celle sur pandas et ajouter une partie "Pour aller plus loin", qui contient pour l’instant un chapitre sur les regex (elle sera sûrement amenée à être complétée par d’autres chapitres concernant d’autres notions complémentaires).

Merci d’avance pour vos commentaires.

Bonne journée,

@flopy78

+0 -0

Voici ce qui sera (je l’espère) la dernière version bêta de mon tutoriel

AMHA, ton tuto est encore loin d’être publiable. Il y a encore de nombreuses erreurs et maladresses un peu partout y compris dans la partie sur pathlib, et y compris certaines qui ont déjà été signalées. La naïveté de certaines de tes explications me laisse penser que tu es débutant avec Python et avec la programmation en général. Comme déjà mentionné dans ce fil ou celui de l’autre tuto, ce n’est pas nécessairement bloquant pour écrire un tutoriel, mais mécaniquement ça veut dire que l’effort à fournir de ta part pour fournir un contenu de qualité est conséquent. Tu ne peux pas contourner le fait de lire et comprendre la documentation de ce que tu présentes ; Python est un langage un peu trompeur et plus complexe qu’il n’y parait en premier abord. Par exemple with n’est pas juste une syntaxe spéciale pour les flux comme tu le prétends (probablement à l’improviste…), c’est une syntaxe qui fait appel à la notion de context manager qui encapsule le try ... finally d’une façon qui prête beaucoup moins à confusion en ce qui concerne le flux d’exécution.

Bref, tout ça pour dire qu’il faut que tu sois plus au point sur les notions que tu présentes…

Non, c’est à toi de te mettre à niveau sur ce que tu veux présenter. On est pas là pour lire la documentation ni écrire ton tuto à ta place, ce serait contre-productif. On peut te guider (par exemple comme on t’a orienté vers pathlib à la place de os.path) pour que tu saches où aller pêcher l’info, et on peut évidemment t’expliquer si il y a des points de la documentation que tu ne comprends pas bien ou des questions d’usages précises qui te viennent. Mais on va pas s’amuser à corriger dans le menu détail un contenu écrit par quelqu’un qui ne connait visiblement pas bien le sujet présenté. C’est à toi d’écrémer le plus gros des erreurs.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte