Die Bibliothek pathlib

Einführung

Es ist schwierig, ein Python-Skript zu schreiben, das keine Interaktion mit dem Dateisystem hat. Die Aktivität könnte so einfach wie das Lesen einer Datei in ein Pandas DataFrame sein oder so komplex wie das Parsen von Tausenden von Dateien in einer tief verschachtelten Verzeichnisstruktur. Die Standardbibliothek von Python bietet mehrere nützliche Funktionen für diese Aufgaben – einschließlich des pathlib-Moduls.

Das pathlib-Modul wurde erstmals in Python 3.4 aufgenommen und wurde in jeder der darauf folgenden Versionen verbessert. Pathlib ist eine objektorientierte Schnittstelle zum Dateisystem und bietet eine intuitivere Methode, um mit dem Dateisystem plattformunabhängig und auf Python-Art zu interagieren.

Erste Schritte mit Pathlib

Die pathlib-Bibliothek ist in allen Versionen von Python >= 3.4 enthalten.

Eine der nützlichen Funktionen des pathlib-Moduls ist, dass es intuitiver ist, Pfade aufzubauen, ohne os.joindir zu verwenden. 

import os

in_dir = os.path.join(os.getcwd(), "in")
out_dir = os.path.join(os.getcwd(), "out")
in_file = os.path.join(in_dir, "input.xlsx")
out_file = os.path.join(out_dir, "output.xlsx")

Das funktioniert, ist aber ein wenig umständlich. Wenn ich zum Beispiel nur die Eingabe- und Ausgabedateien definieren möchte, ohne die Verzeichnisse zu definieren, sieht es so aus:

import os

in_file = os.path.join(os.path.join(os.getcwd(), "in"), "input.xlsx")
out_file = os.path.join(os.path.join(os.getcwd(), "out"), "output.xlsx")

Das ist nicht komplex, aber auch nicht schön.

Schauen wir uns an, wie es aussieht, wenn wir das pathlib-Modul verwenden.

from pathlib import Path

in_file_1 = Path.cwd() / "in" / "input.xlsx"
out_file_1 = Path.cwd() / "out" / "output.xlsx"

Meiner Meinung nach ist dies viel einfacher mental zu parsen. Es handelt sich um einen ähnlichen Denkprozess wie die os.path-Methode zum Verbinden des aktuellen Arbeitsverzeichnisses (unter Verwendung von Path.cwd() ) mit den verschiedenen Unterverzeichnissen und Dateispeicherorten. Es ist viel einfacher zu verfolgen, da das clevere Überschreiben des / einen Pfad auf eine natürlichere Weise aufbaut als das Verknüpfen vieler os.path.join-Methoden zusammen.

Zusätzlich kann man, wenn einem die obige Syntax nicht gefällt, mehrere Teile mithilfe von joinpath verketten:

in_file_2 = Path.cwd().joinpath("in").joinpath("input.xlsx")
out_file_2 = Path.cwd().joinpath("out").joinpath("output.xlsx")

Dies ist meiner Meinung nach etwas umständlicher, aber immer noch viel besser als der obige os.path.join-Wahnsinn.

Schließlich gibt es noch einen weiteren Trick, mit dem man einen Pfad mit mehreren Verzeichnissen erstellen kann:

parts = ["in", "input.xlsx"]
in_file_3 = Path.cwd().joinpath(*parts)

Unabhängig von der Methode, die man verwendet, funktionieren diese Ansätze, um einen Pfad zu einer Datei oder einem Verzeichnis zu erstellen. Der zusätzliche Vorteil dieser Methoden ist, dass man ein Path-Objekt und nicht nur eine String-Darstellung des Pfades erstellt. 

print(in_file)
print(type(in_file))

/home/user/src/in/input.xlsx
<class 'str'>

Die Ausgabe von os.path.join ist eine normale Zeichenkette. Vergleichen Sie dies mit den verschiedenen pathlib-Ansätzen:

print(in_file_1)
print(type(in_file_1))

/home/user/src/in/input.xlsx
<class 'pathlib.PosixPath'>

Die tatsächliche Zeichenfolgendarstellung ist dieselbe, aber der Variablentyp ist ein pathlib.PosixPath. Die Tatsache, dass der Pfad ein Objekt ist, bedeutet, dass wir viele nützliche Aktionen mit dem Objekt ausführen können. Es ist auch interessant, dass das Pfadobjekt „weiß“, dass es auf einem Linux-System (auch Posix genannt) ist und es intern so darstellt, ohne dass der Programmierer es ihm sagen muss. Der Vorteil ist, dass der Code auf einem Windows-Computer genauso ausgeführt wird und dass die zugrunde liegende Bibliothek sich um (mögliche) Windows- Eigenheiten kümmert.

Arbeiten mit Pfadobjekten 

Nun, da wir die Grundlagen der Erstellung eines Pfadobjekts kennen, können wir uns ansehen, was wir mit dem Objekt tun können. Für dieses Beispiel wird eine einfache verschachtelte Struktur verwendet, die eine Mischung aus CSV- und Excel-Dateien enthält und auf einem externen USB-Laufwerk gespeichert ist. 

Um mit den Beispielen zu beginnen, erstellen wir den Pfad zum Verzeichnis data_analysis:

from pathlib import Path

dir_to_scan = "/media/KINGSTON/data_analysis"
p = Path(dir_to_scan)

Dieses Beispiel zeigt, wie man eine vollständige Zeichenkette verwendet, um ein Pfadobjekt zu erstellen. In diesem Fall übergibt man den vollständigen Pfad zum USB-Laufwerk. Schauen wir uns an, was wir mit dem p-Objekt machen können.

p.is_dir()
True

p.is_file()
False

p.parts
('/', 'media', 'KINGSTON', 'data_analysis')

p.absolute()
PosixPath('/media/KINGSTON/data_analysis')

p.anchor
'/'

p.as_uri()
'file:///media/KINGSTON/data_analysis'

p.parent
PosixPath('/media/KINGSTON')

Es ist ziemlich einfach, die Ergebnisse aus diesem Objekt zu verwenden und zu interpretieren. Es sind viele andere Funktionen dieser API verfügbar.

Außerhalb der Überprüfung des Pfads auf verschiedene Arten besteht ein sehr häufiges Bedürfnis darin, alle Dateien und Verzeichnisse innerhalb eines bestimmten Verzeichnisses zu analysieren. Die Python-Standardbibliothek verfügt über mehrere Methoden, um alle Dateien und Unterverzeichnisse in einem Pfad zu durchlaufen. 

Verzeichnisse durchlaufen 

Der erste Ansatz, besteht darin, die Funktion os.scandir zu verwenden, um alle Dateien und Verzeichnisse in einem bestimmten Pfad zu analysieren und eine Liste aller Verzeichnisse und aller Dateien zu erstellen.

folders = []
files = []

for entry in os.scandir(p):
    if entry.is_dir():
        folders.append(entry)
    elif entry.is_file():
        files.append(entry)

print("Folders - {}".format(folders))
print("Files - {}".format(files))
Folders - [<DirEntry 'Scorecard_Raw_Data'>]
Files - [<DirEntry 'HS_ARCHIVE9302017.xls'>]

Die wichtigen Punkte, die man bei diesem Ansatz beachten sollte, sind, dass er nicht automatisch durch Unterordner navigiert und die zurückgegebenen Elemente DirEntry-Objekte sind. Das bedeutet, dass man sie manuell in Path-Objekte umwandeln muss, wenn man diese Funktionalität benötigt.

Wenn man durch alle Unterordner parsen muss, sollte man os.walk verwenden. 

for dirName, subdirList, fileList in os.walk(p):
    print('Found directory: %s' % dirName)
    for fname in fileList:
        print('\t%s' % fname)


Found directory: /media/KINGSTON/data_analysis
    HS_ARCHIVE9302017.xls
Found directory: /media/KINGSTON/data_analysis/Scorecard_Raw_Data
    MERGED1996_97_PP.csv
    MERGED1997_98_PP.csv
    MERGED1998_99_PP.csv
      <...>
    MERGED2013_14_PP.csv
    MERGED2014_15_PP.csv
    MERGED2015_16_PP.csv
Found directory: /media/KINGSTON/data_analysis/Scorecard_Raw_Data/Crosswalks_20170806
    CW2000.xlsx
    CW2001.xlsx
    CW2002.xlsx
      <...>
    CW2014.xlsx
    CW2015.xlsx
Found directory: /media/KINGSTON/data_analysis/Scorecard_Raw_Data/Crosswalks_20170806/tmp_dir
    CW2002_v3.xlsx
    CW2003_v1.xlsx
    CW2000_v1.xlsx
    CW2001_v2.xlsx

Diese Herangehensweise durchläuft tatsächlich alle Unterverzeichnisse und Dateien, gibt jedoch wiederum einen str-Wert zurück anstelle eines Path-Objekts.

Diese beiden Ansätze ermöglichen eine große manuelle Kontrolle darüber, wie auf die einzelnen Verzeichnisse und Dateien zugegriffen wird. Wenn man einen einfacheren Ansatz benötiget, enthält das Pfadobjekt einige zusätzliche Optionen zum Auflisten von Dateien und Verzeichnissen, die kompakt und nützlich sind.

Der erste Ansatz besteht darin, glob zu verwenden, um alle Dateien in einem Verzeichnis aufzulisten:

for i in p.glob('*.*'):
    print(i.name)

HS_ARCHIVE9302017.xls

Wie Sie sehen können, wird nur die Datei im obersten Verzeichnis ausgegeben. Wenn Sie rekursiv durch alle Verzeichnisse gehen wollen, verwenden Sie die folgende glob-Syntax:

for i in p.glob('**/*.*'):
    print(i.name)


HS_ARCHIVE9302017.xls
MERGED1996_97_PP.csv
    <...>
MERGED2014_15_PP.csv
MERGED2015_16_PP.csv
CW2000.xlsx
CW2001.xlsx
    <...>
CW2015.xlsx
CW2002_v3.xlsx
    <...>
CW2001_v2.xlsx

Es gibt eine weitere Möglichkeit, mit rglob automatisch die Unterverzeichnisse zu durchsuchen. Hier ist eine Verknüpfung, um eine Liste aller csv-Dateien zu erstellen:

list(p.rglob('*.csv'))

[PosixPath('/media/chris/KINGSTON/data_analysis/Scorecard_Raw_Data/MERGED1996_97_PP.csv'),
 PosixPath('/media/chris/KINGSTON/data_analysis/Scorecard_Raw_Data/MERGED1997_98_PP.csv'),
 PosixPath('/media/chris/KINGSTON/data_analysis/Scorecard_Raw_Data/MERGED1998_99_PP.csv'),
    <...>
 PosixPath('/media/chris/KINGSTON/data_analysis/Scorecard_Raw_Data/MERGED2014_15_PP.csv'),
 PosixPath('/media/chris/KINGSTON/data_analysis/Scorecard_Raw_Data/MERGED2015_16_PP.csv')]

 

Diese Syntax kann auch verwendet werden, um Teile einer Datei auszuschließen. In diesem Fall können wir alles außer xlsx-Erweiterungen erhalten:

list(p.rglob('*.[!xlsx]*'))


[PosixPath('/media/chris/KINGSTON/data_analysis/Scorecard_Raw_Data/MERGED1996_97_PP.csv'),
 PosixPath('/media/chris/KINGSTON/data_analysis/Scorecard_Raw_Data/MERGED1997_98_PP.csv'),
 PosixPath('/media/chris/KINGSTON/data_analysis/Scorecard_Raw_Data/MERGED1998_99_PP.csv'),
    <...>
 PosixPath('/media/chris/KINGSTON/data_analysis/Scorecard_Raw_Data/MERGED2014_15_PP.csv'),
 PosixPath('/media/chris/KINGSTON/data_analysis/Scorecard_Raw_Data/MERGED2015_16_PP.csv')]

Es gibt eine kurze Anmerkung, die ich im Zusammenhang mit der Verwendung von glob weitergeben möchte. Die Syntax sieht zwar wie ein regulärer Ausdruck aus, ist aber in Wirklichkeit eine viel eingeschränktere Untermenge. Einige nützliche Ressourcen findet man hier.

Fazit

Eine der Stärken von Python ist, dass es im Laufe der Zeit weiterentwickelt und ausgebaut wird. Das Modul pathlib ist ein hervorragendes Beispiel für die Sorgfalt, die die Pfleger aufwenden, um neue Fähigkeiten aufzubauen, die die Gesamtfähigkeiten von Python verbessern.