Dateien und Verzeichnisse

pathlib implementiert Pfadoperationen mithilfe von pathlib.PurePath- und pathlib.Path-Objekten. Die os- und os.path-Module hingegen bieten Funktionen, die low-level mit str- und bytes arbeiten, was eher einem prozeduralen Ansatz entspricht. Wir halten den objektorientierten Stil von pathlib für besser lesbar und stellen ihn deswegen hier ausführlicher vor.

Siehe auch

Lesen und Schreiben von Dateien

In Python öffnet und lest ihr eine Datei, indem ihr die Funktion pathlib.Path.open() und verschiedene eingebaute Leseoperationen verwendet. pathlib.Path.open() öffnet die Datei, auf die der Pfad verweist, wie es die integrierte Funktion open() tut. Das folgende kurze Python-Programm liest eine Zeile aus einer Textdatei namens myfile.txt in docs/save-data/ ein:

1>>> from pathlib import Path
2>>> p = Path("docs", "save-data", "myfile.txt")
3>>> f = p.open()
4>>> headline = f.readline()
Zeile 2:

Die Argumente von pathlib.Path sind Pfadsegmente, entweder als PosixPath oder WindowsPath. Im vorigen Beispiel öffnet ihr eine Datei, von der ihr annehmt, dass sie sich relativ zu eurem Aufruf im docs/save-data/myfile.txt befindet.

Das folgende Beispiel öffnet eine Datei an einem absoluten Speicherort – C:\My Documents\myfile.txt:

2>>> p = Path("C:/", "Users", "Veit", "My Documents", "myfile.txt")
3>>> with p.open() as f:
4...     f.readline()
5...

Bemerkung

In diesem Beispiel wird das Schlüsselwort with verwendet, d.h., dass die Datei mit einem Kontextmanager geöffnet wird, der in Kontextmanagement mit with näher erläutert wird. Diese Art des Öffnens von Dateien verwaltet mögliche I/O-Fehler besser und sollte im Allgemeinen bevorzugt werden.

Zeile 3:

pathlib.Path.open() liest nichts aus der Datei, sondern gibt ein Datei-Objekt zurück, mit dem ihr auf die geöffnete Datei zugreifen könnt. Es behält den Überblick über eine Datei und darüber, wie viel von der Datei gelesen oder geschrieben wurde. Alle Dateieingaben in Python werden mit Dateiobjekten und nicht mit Dateinamen durchgeführt.

Zeile 4:

Der erste Aufruf von readline() gibt die erste Zeile des Datei-Objekts zurück, also alles bis einschließlich des ersten Zeilenumbruchs oder die gesamte Datei, wenn es keinen Zeilenumbruch in der Datei gibt; der nächste Aufruf von readline() gibt die zweite Zeile zurück, wenn sie existiert usw. Wenn es nichts mehr zu lesen gibt, gibt readline() einen leeren String zurück.

Dieses Verhalten von readline() macht es einfach, z.B. die Anzahl der Zeilen in einer Datei zu ermitteln:

>>> with p.open() as f:
...     lc = 0
...     while f.readline() != "":
...         lc = lc + 1
...     print(lc)
...
2

Ein kürzerer Weg, alle Zeilen zu zählen, gibt es mit der ebenfalls eingebauten readlines()-Methode, die alle Zeilen einer Datei liest und sie als Liste von Strings mit einen String pro Zeile zurückgibt:

>>> with p.open() as f:
...     print(len(f.readlines()))
...
2

Wenn ihr alle Zeilen einer großen Datei zählt, kann diese Methode jedoch dazu führen, dass der Speicher vollläuft, weil die gesamte Datei auf einmal gelesen wird. Es ist auch möglich, dass der Speicher mit readline() überläuft, wenn ihr versucht, eine Zeile aus einer großen Datei zu lesen, die keine Zeilenumbruchzeichen enthält. Um mit solchen Situationen besser umgehen zu können, haben beide Methoden ein optionales Argument, das die Menge der zu einem Zeitpunkt gelesenen Daten beeinflusst. Eine andere Möglichkeit, über alle Zeilen einer Datei zu iterieren, besteht darin, das Dateiobjekt als Iterator in einer for-Schleife zu behandeln:

>>> with p.open() as f:
...     lc = 0
...     for l in f:
...         lc = lc + 1
...     print(lc)
...
2

Diese Methode hat den Vorteil, dass die Zeilen je nach Bedarf in den Speicher eingelesen werden, so dass selbst bei großen Dateien kein Speicherplatzmangel zu befürchten ist. Der andere Vorteil dieser Methode ist, dass sie einfacher und lesbarer ist.

Ein mögliches Problem mit der Lesemethode kann jedoch entstehen, wenn auf Windows- und macOS Übersetzungen im Textmodus erfolgen, wenn ihr den Befehl open() im Textmodus verwenden, d.h. ohne ein b anzuhängen. Im Textmodus wird auf macOS jedes \r in \n umgewandelt, während unter Windows \r\n-Paare in \n umgewandelt werden. Ihr könnt die Behandlung von Zeilenumbrüchen festlegen, indem ihr beim Öffnen der Datei den Parameter newline verwendet und newline="\n", \r oder \r\n angebt, wodurch nur diese Zeichenfolge als Zeilenumbruch verwendet wird:

>>> with p.open(newline="\r\n") as f:
...     lc = 0
...

In diesem Beispiel wird nur \n als Zeilenumbruch gewertet. Wenn die Datei jedoch im Binärmodus geöffnet wurde, ist der Parameter newline nicht erforderlich, da alle Bytes genau so zurückgegeben werden, wie sie in der Datei stehen.

pathlib.Path.read_text()

gibt den dekodierten Inhalt der angegebenen Datei als Zeichenkette zurück:

>>> p.read_text()
'This is the first line of myfile.\nAnd this is another line.\n'
pathlib.Path.write_text()

öffnet die angegebene Datei im Textmodus, schreibt Daten in sie und schließt die Datei:

>>> p.write_text("New content")
11
>>> p.read_text()
'New content'

Eine vorhandene Datei mit demselben Namen wird überschrieben.

Verzeichnisse lesen

pathlib.Path.iterdir()

Wenn der Pfad auf ein Verzeichnis verweist, werden die Pfadobjekte des Verzeichnisinhalts zurückgegeben:

>>> p = Path("docs", "save-data")
>>> for child in p.iterdir():
...     child
...
PosixPath('docs/save-data/index.rst')
PosixPath('docs/save-data/minidom_example.py')
PosixPath('docs/save-data/pickle.rst')
PosixPath('docs/save-data/xml.rst')
PosixPath('docs/save-data/books.xml')
PosixPath('docs/save-data/files.rst')

Die Kind-Objekte werden in beliebiger Reihenfolge zurückgegeben, und die speziellen Einträge . und .. sind nicht enthalten. Wenn der Pfad kein Verzeichnis oder anderweitig nicht zugänglich ist, wird ein OSError ausgelöst.

pathlib.Path.glob()

findet das angegebene relative Muster im Verzeichnis, das durch diesen Pfad dargestellt wird, und gibt alle übereinstimmenden Dateien aus:

>>> sorted(p.glob("*.rst"))
[PosixPath('docs/save-data/files.rst'), PosixPath('docs/save-data/index.rst'), PosixPath('docs/save-data/pickle.rst'), PosixPath('docs/save-data/xml.rst')]

Siehe auch

Pattern language

pathlib.Path.rglob()

findet das angegebene relative Muster rekursiv. Dies entspricht dem Aufruf mit **/ vor dem Muster.

pathlib.Path.walk()

generiert die Dateinamen in einer Verzeichnisstruktur, indem es die Struktur entweder von oben nach unten oder von unten nach oben durchläuft. Zurückgegeben wird ein 3-Tupel aus (dirpath, dirnames, filenames).

Bei der Standardeinstellung des optionalen Arguments top_down=True wird das Triple für ein Verzeichnis vor den Triples für seine Unterverzeichnisse generiert.

Bei follow_symlinks=True werden Symlinks aufgelöst und sie entsprechend ihren Zielen in dirnames und filenames platziert.

Das folgende Beispiel zeigt die Größe der Dateien in einem Verzeichnis an, wobei __pycache__-Verzeichnisse ignoriert werden:

>>> for root, dirs, files in p.walk():
...     print(
...         root,
...         "consumes",
...         sum((root / file).stat().st_size for file in files),
...         "bytes in",
...         len(files),
...         "non-directory files",
...     )
...     if "__pycache__" in dirs:
...         dirs.remove("__pycache__")
...
docs/save-data consumes 88417 bytes in 13 non-directory files
docs/save-data/sqlite consumes 35187 bytes in 19 non-directory files

Das nächste Beispiel ist eine einfache Implementierung von shutil.rmtree(), wobei der Verzeichnisbaum von unten nach oben durchlaufen werden muss, da pathlib.Path.rmdir() das Löschen eines Verzeichnisses erst zulässt, wenn es leer ist:

>>> for root, dirs, files in p.walk(top_down=False):
...     for name in files:
...         (root / name).unlink()
...     for name in dirs:
...         (root / name).rmdir()
...

Added in version 3.12.

Erstellen von Dateien und Verzeichnissen

pathlib.Path.touch()

erstellt eine Datei unter dem angegebenen Pfad. Mit mode lassen sich Dateimodus und die Zugriffsflags angeben Wenn die Datei bereits existiert, wird die Änderungszeit auf die aktuelle Zeit aktualisiert sofern exist_ok=True ist, andernfalls wird ein FileExistsError ausgelöst.

Bemerkung

Um Dateien zu erstellen wird auch häufig pathlib.Path.open() oder pathlib.Path.write_text() verwendet.

pathlib.Path.mkdir()

erstellt ein neues Verzeichnis unter dem angegebenen Pfad. Die Parameter mode und exist_ok funktionieren wie in pathlib.Path.touch() angegeben.

Wenn parents=True, werden fehlende übergeordnete Verzeichnisse des Pfads nach Bedarf mit den Standardberechtigungen erstellt. Mit der Standardeinstellung parents=False wird hingegen FileNotFoundError ausgelöst.

Verschieben, Kopieren und Löschen

pathlib.Path.rename()

benennt die Datei oder das Verzeichnis in das angegebene Ziel um und gibt eine neue pathlib.Path-Instanz zurück, die auf das Ziel verweist. Unter Unix wird das Ziel, sofern es existiert und eine Datei ist, einfach ersetzt, unter Windows wird ein FileExistsError ausgelöst.

>>> myfile = Path("docs", "save-data", "myfile.txt")
>>> newfile = Path("docs", "newdir", "newfile.txt")
>>> myfile.rename(newfile)
PosixPath('docs/newdir/newfile.txt')

Added in version 3.14: In Python 3.14 kommen die Methoden pathlib.Path.copy(), pathlib.Path.copy_into(), pathlib.Path.move() und pathlib.Path.move_into() hinzu.

Berechtigungen und Eigentum

pathlib.Path.owner()

gibt den Namen der Person zurück, der die Datei gehört. Normalerweise wird Symlinks gefolgt, wenn ihr jedoch die Person ermitteln wollt, der der Symlink gehört, fügt follow_symlinks=False hinzu. Wenn die User-ID (UID) der Datei nicht gefunden wird, wird KeyError ausgelöst.

pathlib.Path.group()

gibt den Namen der Gruppe zurück, der die Datei gehört. Das Verhalten bei Symlinks entspricht dem vom pathlib.Path.owner(). Und wenn die Group-ID (GID) der Datei nicht gefunden wird, wird ebenfalls KeyError ausgelöst.

pathlib.Path.chmod()

ändert den Dateimodus und die Berechtigungen.Dabei wird normalerweise Symlinks gefolgt. Für das Ändern der Symlink-Berechtigungen, könnt ihr follow_symlinks=False oder pathlib.Path.lchmod() verwenden.

Vergleich mit os und os.path

  • pathlib implementiert mit pathlib.PurePath und pathlib.Path Objekte, während os und os.path eher prozedural mit low-level str- und Bytes arbeiten.

  • Viele Funktionen in os und os.path unterstützen Pfade relativ zu Verzeichnisdeskriptoren. Diese Funktionen sind in pathlib nicht verfügbar.

  • str und bytes sowie Teile von :mod:`python3:os und python3:`os.path sind in C geschrieben und sehr schnell. pathlib ist hingegen in Python geschrieben und oft langsamer, aber nicht immer spielt dies eine Rolle.

Trotz der Unterschiede lassen sich viele os-Funktionen in entsprechende pathlib.Path- oder pathlib.PurePath-Funktionen übersetzen:

Checks

  • Verwendet die Funktionen des pathlib-Moduls, um einen Pfad zu einer Datei namens instance.log zu nehmen und einen neuen Dateipfad im selben Verzeichnis für eine Datei namens instance.log1 zu erstellen.

  • Öffnet eine Datei my_file.txt und fügt zusätzlichen Text am Ende der Datei ein. Welchen Befehl würdet ihr verwenden, um my_file.txt zu öffnen? Welchen Befehl würdet ihr verwenden, um die Datei erneut zu öffnen und von Anfang an zu lesen?

  • Wenn ihr euch die Manpage für das wc-Dienstprogramm anseht, seht ihr zwei Befehlszeilenoptionen:

    -c

    zählt die Bytes in der Datei

    -m

    zählt die Zeichen, die im Falle einiger Unicode-Zeichen zwei oder mehr Bytes lang sein können

    Außerdem sollte unser Modul, wenn eine Datei angegeben wird, aus dieser Datei lesen und sie verarbeiten, aber wenn keine Datei angegeben wird, sollte es aus stdin lesen und verarbeiten.

  • Wenn ein Kontext-Manager in einem Skript verwendet wird, das mehrere Dateien liest und/oder schreibt, welche der folgenden Ansätze wäre eurer Meinung nach am besten?

    1. Legt das gesamte Skript in einen Block, der von einer with-Anweisung verwaltet wird.

    2. Verwendet eine with-Anweisung für alle Lesevorgänge und eine weitere für alle Schreibvorgänge.

    3. Verwendet jedes Mal eine with-Anweisung, wenn ihr eine Datei lest oder schreibt, d.h. für jede Zeile.

    4. Verwendet für jede Datei, die ihr lest oder schreibt, eine with-Anweisung.

  • Archiviert *.txt-Dateien aus dem aktuellen Verzeichnis im Verzeichnis archive als *.zip-Dateien mit dem aktuellen Datum als Dateiname.

    • Welche Module benötigt ihr hierfür?

    • Schreibt eine mögliche Lösung.