Module pathlib de Python 3: Apprivoiser le système de fichiers

Module pathlib de Python 3: Apprivoiser le système de fichiers

Avez-vous rencontré des difficultés avec la gestion du chemin d’accès aux fichiers en Python? En Python 3.4 et supérieur, la lutte est maintenant terminée! Vous n’avez plus besoin de vous gratter la tête sur du code comme:

>>>

>>> path.rsplit('\\', maxsplit=1)[0]

Ou grincer des dents à la verbosité de:

>>>

>>> os.path.isfile(os.path.join(os.path.expanduser('~'), 'realpython.txt'))

Dans ce didacticiel, vous allez voir comment utiliser les chemins de fichiers - noms de répertoires et fichiers - en Python. Vous apprendrez de nouvelles façons de lire et d’écrire des fichiers, de manipuler des chemins et le système de fichiers sous-jacent, ainsi que de voir quelques exemples de la façon de répertorier les fichiers et de les parcourir. En utilisant le module + pathlib +, les deux exemples ci-dessus peuvent être réécrits en utilisant du code élégant, lisible et Pythonic comme:

>>>

>>> path.parent
>>> (pathlib.Path.home()/'realpython.txt').is_file()
*Téléchargement PDF gratuit:* https://realpython.com/bonus/python-cheat-sheet-short/[Python 3 Cheat Sheet]

Le problème avec la gestion du chemin du fichier Python

Travailler avec des fichiers et interagir avec le système de fichiers est important pour de nombreuses raisons différentes. Les cas les plus simples peuvent impliquer uniquement la lecture ou l’écriture de fichiers, mais parfois des tâches plus complexes sont à portée de main. Vous devrez peut-être répertorier tous les fichiers d’un répertoire d’un type donné, trouver le répertoire parent d’un fichier donné ou créer un nom de fichier unique qui n’existe pas déjà.

Traditionnellement, Python a représenté les chemins de fichiers à l’aide de chaînes de texte normales. Avec le support de la bibliothèque standard https://docs.python.org/3/library/os.path.html [+ os.path +], cela a été suffisant bien qu’un peu lourd (comme le deuxième exemple dans l’introduction). spectacles). Cependant, puisque paths ne sont pas des chaînes, des fonctionnalités importantes sont réparties tout autour de la bibliothèque standard, y compris des bibliothèques comme https://docs.python.org/3/library/os.html [+ os +], https://docs.python.org/3/library/glob.html [+ glob +] et https://docs.python.org/3/library/shutil.html [+ shutil +]. L’exemple suivant nécessite trois instructions `+ import + 'juste pour déplacer tous les fichiers texte dans un répertoire d’archives:

import glob
import os
import shutil

for file_name in glob.glob('*.txt'):
    new_path = os.path.join('archive', file_name)
    shutil.move(file_name, new_path)

Avec les chemins représentés par des chaînes, il est possible, mais généralement une mauvaise idée, d’utiliser des méthodes de chaîne régulières. Par exemple, au lieu de joindre deux chemins avec + comme des chaînes régulières, vous devez utiliser + os.path.join () +, qui joint les chemins en utilisant le séparateur de chemin correct sur le système d’exploitation. Rappelons que Windows utilise + \ + tandis que Mac et Linux utilisent / comme séparateur. Cette différence peut conduire à des erreurs difficiles à repérer, comme notre premier exemple dans l’introduction fonctionnant uniquement pour les chemins Windows.

Le module + pathlib + a été introduit dans Python 3.4 (PEP 428) pour faire face à ces défis. Il rassemble les fonctionnalités nécessaires en un seul endroit et les rend disponibles via des méthodes et des propriétés sur un objet + Path + facile à utiliser.

Au début, d’autres packages utilisaient toujours des chaînes pour les chemins de fichiers, mais à partir de Python 3.6, le module + pathlib + est pris en charge dans toute la bibliothèque standard, en partie grâce à l’ajout d’un protocole de chemin d’accès au système de fichiers. Si vous êtes bloqué sur l’héritage Python, il existe également un backport disponible pour Python 2.

Il est temps d’agir: voyons comment «+ pathlib +» fonctionne dans la pratique.

Création de chemins

Tout ce que vous devez vraiment savoir, c’est la classe + pathlib.Path +. Il existe différentes manières de créer un chemin. Tout d’abord, il existe classmethods comme + .cwd () + (répertoire de travail actuel) et `+ .home () + `(répertoire personnel de votre utilisateur):

>>>

>>> import pathlib
>>> pathlib.Path.cwd()
PosixPath('/home/gahjelle/realpython/')

_ Remarque: Tout au long de ce didacticiel, nous supposerons que + pathlib + a été importé, sans préciser + import pathlib + comme ci-dessus. Comme vous utiliserez principalement la classe + Path +, vous pouvez également faire + à partir de pathlib import Path + 'et écrire + Path + au lieu de + pathlib.Path + `. _

Un chemin peut également être créé explicitement à partir de sa représentation sous forme de chaîne:

>>>

>>> pathlib.Path(r'C:\Users\gahjelle\realpython\file.txt')
WindowsPath('C:/Users/gahjelle/realpython/file.txt')

Un petit conseil pour gérer les chemins Windows: sous Windows, le séparateur de chemin est une barre oblique inverse, + \ +. Cependant, dans de nombreux contextes, la barre oblique inverse est également utilisée en tant que _ caractère d’échappement_ afin de représenter des caractères non imprimables. Pour éviter les problèmes, utilisez raw string literals pour représenter les chemins d’accès Windows. Ce sont des littéraux de chaîne qui ont un + r + ajouté. Dans les littéraux de chaîne bruts, le + \ + représente une barre oblique inverse littérale: + r’C: \ Users '+.

Une troisième façon de construire un chemin est de joindre les parties du chemin en utilisant l’opérateur spécial /. L’opérateur de barre oblique est utilisé indépendamment du séparateur de chemin réel sur la plate-forme:

>>>

>>> pathlib.Path.home()/'python'/'scripts'/'test.py'
PosixPath('/home/gahjelle/python/scripts/test.py')

Le / peut joindre plusieurs chemins ou un mélange de chemins et de chaînes (comme ci-dessus) tant qu’il y a au moins un objet + Chemin +. Si vous n’aimez pas la notation spéciale /, vous pouvez faire la même chose avec la méthode + .joinpath () +:

>>>

>>> pathlib.Path.home().joinpath('python', 'scripts', 'test.py')
PosixPath('/home/gahjelle/python/scripts/test.py')

Notez que dans les exemples précédents, le + pathlib.Path + est représenté par un + WindowsPath + ou un + PosixPath +. L’objet réel représentant le chemin dépend du système d’exploitation sous-jacent. (Autrement dit, l’exemple + WindowsPath + a été exécuté sur Windows, tandis que les exemples + PosixPath + ont été exécutés sur Mac ou Linux.) Voir le lien de la section: # operating-system-differences [Operating System Differences] pour plus d’informations .

Lecture et écriture de fichiers

Traditionnellement, la façon de lire ou d’écrire un fichier en Python a été d’utiliser la fonction intégrée + open () +. Ceci est toujours vrai car la fonction + open () + peut utiliser directement les objets + Path +. L’exemple suivant recherche tous les en-têtes dans un fichier Markdown et les imprime:

path = pathlib.Path.cwd()/'test.md'
with open(path, mode='r') as fid:
    headers = [line.strip() for line in fid if line.startswith('#')]
print('\n'.join(headers))

Une alternative équivalente consiste à appeler + .open () + sur l’objet + Path +:

with path.open(mode='r') as fid:
    ...

En fait, + Path.open () + appelle le + open () + intégré dans les coulisses. L’option que vous utilisez est principalement une question de goût.

Pour la lecture et l’écriture simples de fichiers, il existe quelques méthodes pratiques dans la bibliothèque + pathlib +:

  • + .read_text () +: ouvrez le chemin en mode texte et renvoyez le contenu sous forme de chaîne.

  • + .read_bytes () +: ouvre le chemin en mode binaire/octets et retourne le contenu sous forme de bytestring.

  • + .write_text () +: ouvrez le chemin et écrivez-y les données de la chaîne.

  • + .write_bytes () +: ouvrez le chemin en mode binaire/octets et écrivez-y des données.

Chacune de ces méthodes gère l’ouverture et la fermeture du fichier, ce qui les rend triviales à utiliser, par exemple:

>>>

>>> path = pathlib.Path.cwd()/'test.md'
>>> path.read_text()
<the contents of the test.md-file>

Les chemins peuvent également être spécifiés comme de simples noms de fichiers, auquel cas ils sont interprétés par rapport au répertoire de travail actuel. L’exemple suivant est équivalent au précédent:

>>>

>>> pathlib.Path('test.md').read_text()
<the contents of the test.md-file>

La méthode + .resolve () + trouvera le chemin complet. Ci-dessous, nous confirmons que le répertoire de travail actuel est utilisé pour les noms de fichiers simples:

>>>

>>> path = pathlib.Path('test.md')
>>> path.resolve()
PosixPath('/home/gahjelle/realpython/test.md')
>>> path.resolve().parent == pathlib.Path.cwd()
False

Notez que lorsque les chemins sont comparés, ce sont leurs représentations qui sont comparées. Dans l’exemple ci-dessus, + path.parent + n’est pas égal à + ​​pathlib.Path.cwd () +, car + path.parent + est représenté par + '.' + Tandis que + pathlib.Path .cwd () + `est représenté par + '/home/gahjelle/realpython/' + `.

Récupération des composants d’un chemin

Les différentes parties d’un chemin sont facilement disponibles en tant que propriétés. Les exemples de base incluent:

  • + .name +: le nom du fichier sans aucun répertoire

  • + .parent +: le répertoire contenant le fichier, ou le répertoire parent si le chemin est un répertoire

  • + .stem +: le nom du fichier sans le suffixe

  • + .suffix +: l’extension de fichier

  • + .anchor +: la partie du chemin avant les répertoires

Voici ces propriétés en action:

>>>

>>> path
PosixPath('/home/gahjelle/realpython/test.md')
>>> path.name
'test.md'
>>> path.stem
'test'
>>> path.suffix
'.md'
>>> path.parent
PosixPath('/home/gahjelle/realpython')
>>> path.parent.parent
PosixPath('/home/gahjelle')
>>> path.anchor
'/'

Notez que + .parent + renvoie un nouvel objet + Path +, tandis que les autres propriétés renvoient des chaînes. Cela signifie par exemple que + .parent + peut être chaîné comme dans le dernier exemple ou même combiné avec / pour créer des chemins complètement nouveaux:

>>>

>>> path.parent.parent/('new' + path.suffix)
PosixPath('/home/gahjelle/new.md')

L’excellente Pathlib Cheatsheet fournit une représentation visuelle de ces propriétés et méthodes ainsi que d’autres.

Déplacer et supprimer des fichiers

Grâce à + ​​pathlib +, vous avez également accès aux opérations de base du système de fichiers comme le déplacement, la mise à jour et même la suppression de fichiers. Pour la plupart, ces méthodes ne donnent pas d’avertissement et n’attendent pas de confirmation avant de perdre des informations ou des fichiers. Soyez prudent lorsque vous utilisez ces méthodes.

Pour déplacer un fichier, utilisez + .replace () +. Notez que si la destination existe déjà, + .replace () + la remplacera. Malheureusement, + pathlib + ne prend pas explicitement en charge le déplacement sécurisé des fichiers. Pour éviter éventuellement d’écraser le chemin de destination, le plus simple est de tester si la destination existe avant de remplacer:

if not destination.exists():
    source.replace(destination)

Cependant, cela laisse la porte ouverte pour une éventuelle condition de concurrence. Un autre processus peut ajouter un fichier au chemin + destination + entre l’exécution de l’instruction + if + et la méthode + .replace () +. Si cela vous inquiète, un moyen plus sûr consiste à ouvrir le chemin de destination pour exclusive creation et à copier explicitement les données source:

with destination.open(mode='xb') as fid:
    fid.write(source.read_bytes())

Le code ci-dessus déclenchera un + FileExistsError + si + destination + existe déjà. Techniquement, cela copie un fichier. Pour effectuer un déplacement, supprimez simplement + source + une fois la copie effectuée (voir ci-dessous). Assurez-vous cependant qu’aucune exception n’a été levée.

Lorsque vous renommez des fichiers, les méthodes utiles peuvent être + .with_name () + et + .with_suffix () +. Ils renvoient tous les deux le chemin d’origine mais avec le nom ou le suffixe remplacés, respectivement.

Par exemple:

>>>

>>> path
PosixPath('/home/gahjelle/realpython/test001.txt')
>>> path.with_suffix('.py')
PosixPath('/home/gahjelle/realpython/test001.py')
>>> path.replace(path.with_suffix('.py'))

Les répertoires et les fichiers peuvent être supprimés en utilisant respectivement + .rmdir () + et + .unlink () +. (Encore une fois, faites attention!)

Exemples

Dans cette section, vous verrez quelques exemples d’utilisation de + pathlib + pour faire face à des défis simples.

Compter les fichiers

Il existe plusieurs façons de répertorier de nombreux fichiers. La plus simple est la méthode + .iterdir () +, qui itère sur tous les fichiers du répertoire donné. L’exemple suivant combine + .iterdir () + avec la classe + collections.Counter + pour compter le nombre de fichiers de chaque type de fichier dans le répertoire en cours:

>>>

>>> import collections
>>> collections.Counter(p.suffix for p in pathlib.Path.cwd().iterdir())
Counter({'.md': 2, '.txt': 4, '.pdf': 2, '.py': 1})

Des listes de fichiers plus flexibles peuvent être créées avec les méthodes + .glob () + et + .rglob () + (glob récursif). Par exemple, + pathlib.Path.cwd (). Glob ('*. Txt') + renvoie tous les fichiers avec le suffixe + .txt + dans le répertoire courant. Ce qui suit ne compte que les types de fichiers commençant par + p +:

>>>

>>> import collections
>>> collections.Counter(p.suffix for p in pathlib.Path.cwd().glob('*.p*'))
Counter({'.pdf': 2, '.py': 1})

Afficher une arborescence de répertoires

L’exemple suivant définit une fonction, + tree () +, qui imprimera un arbre visuel représentant la hiérarchie des fichiers, enraciné dans un répertoire donné. Ici, nous voulons également lister les sous-répertoires, nous utilisons donc la méthode + .rglob () +:

def tree(directory):
    print(f'+ {directory}')
    for path in sorted(directory.rglob('*')):
        depth = len(path.relative_to(directory).parts)
        spacer = '    ' * depth
        print(f'{spacer}+ {path.name}')

Notez que nous devons savoir à quelle distance du répertoire racine se trouve un fichier. Pour ce faire, nous utilisons d’abord + .relative_to () + pour représenter un chemin par rapport au répertoire racine. Ensuite, nous comptons le nombre de répertoires (en utilisant la propriété + .parts +) dans la représentation. Lorsqu’elle est exécutée, cette fonction crée un arbre visuel comme suit:

>>>

>>> tree(pathlib.Path.cwd())
+/home/gahjelle/realpython
    + directory_1
        + file_a.md
    + directory_2
        + file_a.md
        + file_b.pdf
        + file_c.py
    + file_1.txt
    + file_2.txt

_ Remarque: f-strings ne fonctionne qu’en Python 3.6 et versions ultérieures. Dans les anciens Pythons, l’expression + f '{spacer} + {path.name}' + peut s’écrire + '{0} + {1}'. Format (spacer, path.name) +. _

Trouver le dernier fichier modifié

Les méthodes + .iterdir () +, + .glob () + et + .rglob () + conviennent parfaitement aux expressions de générateur et aux listes de compréhension. Pour rechercher le fichier dans un répertoire modifié en dernier lieu, vous pouvez utiliser la méthode + .stat () + pour obtenir des informations sur les fichiers sous-jacents. Par exemple, + .stat (). St_mtime + donne l’heure de la dernière modification d’un fichier:

>>>

>>> from datetime import datetime
>>> time, file_path = max((f.stat().st_mtime, f) for f in directory.iterdir())
>>> print(datetime.fromtimestamp(time), file_path)
2018-03-23 19:23:56.977817/home/gahjelle/realpython/test001.txt

Vous pouvez même obtenir le contenu du dernier fichier modifié avec une expression similaire:

>>>

>>> max((f.stat().st_mtime, f) for f in directory.iterdir())[1].read_text()
<the contents of the last modified file in directory>

L’horodatage renvoyé par les différentes propriétés + .stat (). St_ + représente les secondes depuis le 1er janvier 1970. En plus de + datetime.fromtimestamp + ', + time.localtime + ou + time.ctime + `peut être utilisé pour convertir l’horodatage en quelque chose de plus utilisable.

Créer un nom de fichier unique

Le dernier exemple montrera comment construire un nom de fichier numéroté unique basé sur un modèle. Tout d’abord, spécifiez un modèle pour le nom de fichier, avec de la place pour un compteur. Ensuite, vérifiez l’existence du chemin de fichier créé en joignant un répertoire et le nom du fichier (avec une valeur pour le compteur). S’il existe déjà, augmentez le compteur et réessayez:

def unique_path(directory, name_pattern):
    counter = 0
    while True:
        counter += 1
        path = directory/name_pattern.format(counter)
        if not path.exists():
            return path

path = unique_path(pathlib.Path.cwd(), 'test{:03d}.txt')

Si le répertoire contient déjà les fichiers + test001.txt + et + test002.txt +, le code ci-dessus définira + path + sur + test003.txt +.

Différences de système d’exploitation

Plus tôt, nous avons noté que lorsque nous avons instancié + pathlib.Path +, un objet + WindowsPath + ou un objet + PosixPath + a été renvoyé. Le type d’objet dépendra du système d’exploitation que vous utilisez. Cette fonctionnalité facilite l’écriture de code compatible multiplateforme. Il est possible de demander explicitement un + WindowsPath + ou un + PosixPath +, mais vous ne limitez votre code à ce système sans aucun avantage. Un chemin concret comme celui-ci ne peut pas être utilisé sur un système différent:

>>>

>>> pathlib.WindowsPath('test.md')
NotImplementedError: cannot instantiate 'WindowsPath' on your system

Il peut arriver que vous ayez besoin d’une représentation d’un chemin d’accès sans accès au système de fichiers sous-jacent (auquel cas il peut également être judicieux de représenter un chemin Windows sur un système non Windows ou vice versa). Cela peut être fait avec des objets + PurePath +. Ces objets prennent en charge les opérations décrites dans le lien: # picking-out-components-of-a-path [section sur les composants de chemin d’accès] mais pas les méthodes qui accèdent au système de fichiers:

>>>

>>> path = pathlib.PureWindowsPath(r'C:\Users\gahjelle\realpython\file.txt')
>>> path.name
'file.txt'
>>> path.parent
PureWindowsPath('C:/Users/gahjelle/realpython')
>>> path.exists()
AttributeError: 'PureWindowsPath' object has no attribute 'exists'

Vous pouvez instancier directement + PureWindowsPath + ou + PurePosixPath + sur tous les systèmes. L’instanciation de + PurePath + renverra l’un de ces objets en fonction du système d’exploitation que vous utilisez.

Chemins en tant qu’objets appropriés

Dans le lien: # the-problem-with-python-file-path-handling [introduction], nous avons brièvement noté que les chemins ne sont pas des chaînes, et une motivation derrière + pathlib + est de représenter le système de fichiers avec les objets appropriés. En fait, la documentation officielle official de + pathlib + est intitulée ` + pathlib + `- Chemins du système de fichiers orientés objet. L’approche Object-oriented est déjà assez visible dans les exemples ci-dessus (surtout si vous la comparez avec l’ancienne façon de faire les choses `+ os.path + ' ). Cependant, permettez-moi de vous laisser avec quelques autres morceaux.

Indépendamment du système d’exploitation que vous utilisez, les chemins sont représentés dans le style Posix, avec la barre oblique comme séparateur de chemin. Sous Windows, vous verrez quelque chose comme ceci:

>>>

>>> pathlib.Path(r'C:\Users\gahjelle\realpython\file.txt')
WindowsPath('C:/Users/gahjelle/realpython/file.txt')

Pourtant, lorsqu’un chemin est converti en chaîne, il utilisera le formulaire natif, par exemple avec des barres obliques inverses sous Windows:

>>>

>>> str(pathlib.Path(r'C:\Users\gahjelle\realpython\file.txt'))
'C:\\Users\\gahjelle\\realpython\\file.txt'

Ceci est particulièrement utile si vous utilisez une bibliothèque qui ne sait pas comment traiter les objets + pathlib.Path +. Il s’agit d’un problème plus important sur les versions Python antérieures à 3.6. Par exemple, dans Python 3.5, la bibliothèque + + configparser + `standard ne peut utiliser que des chemins de chaîne pour lire les fichiers. La façon de gérer de tels cas consiste à effectuer la conversion en chaîne explicitement:

>>>

>>> from configparser import ConfigParser
>>> path = pathlib.Path('config.txt')
>>> cfg = ConfigParser()
>>> cfg.read(path)                     # Error on Python < 3.6
TypeError: 'PosixPath' object is not iterable
>>> cfg.read(str(path))                # Works on Python >= 3.4
['config.txt']

Dans Python 3.6 et versions ultérieures, il est recommandé d’utiliser + os.fspath () + au lieu de + str () + si vous devez effectuer une conversion explicite. C’est un peu plus sûr car cela soulèvera une erreur si vous essayez accidentellement de convertir un objet qui n’est pas pathlike.

La partie la plus inhabituelle de la bibliothèque + pathlib + est probablement l’utilisation de l’opérateur /. Pour un petit aperçu sous le capot, voyons comment cela est mis en œuvre. Voici un exemple de surcharge d’opérateur: le comportement d’un opérateur est modifié en fonction du contexte. Vous avez déjà vu cela auparavant. Réfléchissez à la façon dont «+» signifie différentes choses pour les chaînes et les nombres. Python implémente la surcharge des opérateurs grâce à l’utilisation des méthodes double underscore (a.k.a. méthodes dunder).

L’opérateur / est défini par la méthode + . truediv () +. En fait, si vous jetez un œil au source code of + pathlib +, vous verrez quelque chose comme:

class PurePath(object):

    def __truediv__(self, key):
        return self._make_child((key,))

Conclusion

Depuis Python 3.4, + pathlib + est disponible dans la bibliothèque standard. Avec + pathlib +, les chemins de fichier peuvent être représentés par des objets + Path + appropriés au lieu de chaînes simples comme auparavant. Ces objets font du code traitant des chemins de fichiers:

  • Plus facile à lire, surtout parce que / est utilisé pour joindre les chemins ensemble

  • Plus puissant, avec les méthodes et propriétés les plus nécessaires disponibles directement sur l’objet

  • Plus cohérent entre les systèmes d’exploitation, car les particularités des différents systèmes sont masquées par l’objet + Path +

Dans ce didacticiel, vous avez vu comment créer des objets + Chemin +, lire et écrire des fichiers, manipuler des chemins et le système de fichiers sous-jacent, ainsi que quelques exemples sur la façon d’itérer sur de nombreux chemins de fichiers.

*Téléchargement PDF gratuit:* https://realpython.com/bonus/python-cheat-sheet-short/[Python 3 Cheat Sheet]