PEP 428 – Das pathlib-Modul – objektorientierte Dateisystempfade
- Autor:
- Antoine Pitrou <solipsis at pitrou.net>
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 30-Jul-2012
- Python-Version:
- 3.4
- Post-History:
- 05-Okt-2012
- Resolution:
- Python-Dev Nachricht
Zusammenfassung
Dieser PEP schlägt die Aufnahme eines Drittanbieter-Moduls, pathlib, in die Standardbibliothek vor. Die Aufnahme wird unter dem vorläufigen Label vorgeschlagen, wie in PEP 411 beschrieben. Daher können API-Änderungen entweder als Teil des PEP-Prozesses oder nach der Annahme in die Standardbibliothek (und bis das vorläufige Label entfernt wird) vorgenommen werden.
Ziel dieser Bibliothek ist es, eine einfache Hierarchie von Klassen zur Handhabung von Dateisystempfaden und den gängigen Operationen, die Benutzer damit durchführen, bereitzustellen.
Implementierung
Die Implementierung dieses Vorschlags wird im Branch pep428 des Mercurial-Repositorys von pathlib verfolgt.
Warum eine objektorientierte API
Der Grund für die Darstellung von Dateisystempfaden mithilfe dedizierter Klassen ist derselbe wie bei anderen Arten von zustandslosen Objekten, wie z. B. Daten, Zeiten oder IP-Adressen. Python hat sich langsam von der strikten Nachbildung der C-Sprach-APIs abgewandt, um bessere, hilfreichere Abstraktionen rund um alle Arten gängiger Funktionalitäten bereitzustellen. Selbst wenn dieser PEP nicht angenommen wird, ist es wahrscheinlich, dass eines Tages eine andere Form der Abstraktion zur Dateisystemverwaltung in die Standardbibliothek aufgenommen wird.
Tatsächlich werden viele Leute Daten und Zeiten lieber mit den High-Level-Objekten des datetime-Moduls handhaben, anstatt numerische Zeitstempel und die time-Modul-API zu verwenden. Darüber hinaus ermöglicht die Verwendung einer dedizierten Klasse, wünschenswerte Verhaltensweisen standardmäßig zu aktivieren, z. B. die Groß-/Kleinschreibungsunempfindlichkeit von Windows-Pfaden.
Vorschlag
Klassenhierarchie
Das Modul pathlib implementiert eine einfache Hierarchie von Klassen
+----------+
| |
---------| PurePath |--------
| | | |
| +----------+ |
| | |
| | |
v | v
+---------------+ | +-----------------+
| | | | |
| PurePosixPath | | | PureWindowsPath |
| | | | |
+---------------+ | +-----------------+
| v |
| +------+ |
| | | |
| -------| Path |------ |
| | | | | |
| | +------+ | |
| | | |
| | | |
v v v v
+-----------+ +-------------+
| | | |
| PosixPath | | WindowsPath |
| | | |
+-----------+ +-------------+
Diese Hierarchie teilt Pfadklassen entlang von zwei Dimensionen
- Eine Pfadklasse kann entweder rein (pure) oder konkret (concrete) sein: Reine Klassen unterstützen nur Operationen, die keine tatsächliche E/A erfordern, was die meisten Pfadmanipulationsoperationen sind; konkrete Klassen unterstützen alle Operationen reiner Klassen sowie Operationen, die E/A durchführen.
- Eine Pfadklasse gehört zu einem bestimmten "Flavor" (Art), je nachdem, welche Art von Betriebssystempfaden sie repräsentiert. pathlib implementiert zwei Flavours: Windows-Pfade für die Dateisystemsemantik, die in Windows-Systemen verkörpert ist, und POSIX-Pfade für andere Systeme.
Jede reine Klasse kann auf jedem System instanziiert werden: Sie können zum Beispiel PurePosixPath-Objekte unter Windows, PureWindowsPath-Objekte unter Unix und so weiter manipulieren. Konkrete Klassen können jedoch nur auf einem passenden System instanziiert werden: Tatsächlich wäre es fehleranfällig, E/A mit WindowsPath-Objekten unter Unix oder umgekehrt durchzuführen.
Darüber hinaus gibt es zwei Basisklassen, die auch als systemabhängige Fabriken fungieren: PurePath instanziiert entweder eine PurePosixPath oder eine PureWindowsPath, abhängig vom Betriebssystem. Ebenso instanziiert Path entweder eine PosixPath oder eine WindowsPath.
Es wird erwartet, dass in den meisten Anwendungen die Verwendung der Klasse Path ausreichend ist, weshalb sie den kürzesten Namen von allen hat.
Keine Verwechslung mit Builtins
In diesem Vorschlag erben die Pfadklassen nicht von einem eingebauten Typ. Dies steht im Gegensatz zu einigen anderen Pfadklassen-Vorschlägen, die von str abgeleitet wurden. Sie erheben auch keinen Anspruch darauf, das Sequenzprotokoll zu implementieren: Wenn Sie möchten, dass ein Pfad wie eine Sequenz behandelt wird, müssen Sie auf ein spezielles Attribut (das parts-Attribut) zugreifen.
Der Hauptgrund für die Nicht-Vererbung von str ist die Verhinderung von versehentlichen Operationen zwischen einem String, der einen Pfad repräsentiert, und einem String, der dies nicht tut, z. B. pfad + versehentlich. Da Operationen mit einem String nicht zwangsläufig zu einem gültigen oder erwarteten Dateisystempfad führen, ist "explizit besser als implizit", indem versehentliche Operationen mit Strings durch die Nicht-Unterklassifizierung vermieden werden. Ein Blogbeitrag eines Python Core Developers geht detaillierter auf die Gründe für diese spezifische Designentscheidung ein.
Unveränderlichkeit
Pfadobjekte sind unveränderlich, was sie hashabar macht und auch eine Klasse von Programmierfehlern verhindert.
Sinnvolles Verhalten
Wenig von der Funktionalität von os.path wird wiederverwendet. Viele os.path-Funktionen sind aufgrund von Abwärtskompatibilität an verwirrendes oder schlicht falsches Verhalten gebunden (z. B. die Tatsache, dass os.path.abspath() ".." Pfadkomponenten vereinfacht, ohne zuerst Symlinks aufzulösen).
Vergleiche
Pfade desselben Flavours sind vergleichbar und sortierbar, ob rein oder nicht
>>> PurePosixPath('a') == PurePosixPath('b')
False
>>> PurePosixPath('a') < PurePosixPath('b')
True
>>> PurePosixPath('a') == PosixPath('a')
True
Das Vergleichen und Sortieren von Windows-Pfadobjekten ist unabhängig von Groß-/Kleinschreibung
>>> PureWindowsPath('a') == PureWindowsPath('A')
True
Pfade unterschiedlicher Flavours vergleichen sich immer ungleich und können nicht sortiert werden
>>> PurePosixPath('a') == PureWindowsPath('a')
False
>>> PurePosixPath('a') < PureWindowsPath('a')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: PurePosixPath() < PureWindowsPath()
Pfade vergleichen sich ungleich zu und sind nicht mit Instanzen von eingebauten Typen (wie str) und anderen Typen sortierbar.
Nützliche Notationen
Die API versucht, nützliche Notationen bereitzustellen, ohne dabei Magie zu verwenden. Einige Beispiele
>>> p = Path('/home/antoine/pathlib/setup.py')
>>> p.name
'setup.py'
>>> p.suffix
'.py'
>>> p.root
'/'
>>> p.parts
('/', 'home', 'antoine', 'pathlib', 'setup.py')
>>> p.relative_to('/home/antoine')
PosixPath('pathlib/setup.py')
>>> p.exists()
True
Pure Paths API
Die Philosophie der PurePath-API besteht darin, eine konsistente Auswahl nützlicher Pfadmanipulationsoperationen bereitzustellen, ohne einen Flickenteppich von Funktionen wie os.path preiszugeben.
Definitionen
Zuerst ein paar Konventionen
- Alle Pfade können einen Laufwerksbuchstaben und ein Root-Verzeichnis haben. Bei POSIX-Pfaden ist der Laufwerksbuchstabe immer leer.
- Ein relativer Pfad hat weder Laufwerksbuchstaben noch Root-Verzeichnis.
- Ein POSIX-Pfad ist absolut, wenn er ein Root-Verzeichnis hat. Ein Windows-Pfad ist absolut, wenn er sowohl einen Laufwerksbuchstaben *als auch* ein Root-Verzeichnis hat. Ein Windows UNC-Pfad (z. B.
\\host\share\myfile.txt) hat immer einen Laufwerksbuchstaben und ein Root-Verzeichnis (hier\\host\sharebzw.\). - Ein Pfad, der entweder einen Laufwerksbuchstaben *oder* ein Root-Verzeichnis hat, wird als "verankert" bezeichnet. Sein Anker ist die Verkettung von Laufwerksbuchstabe und Root. Unter POSIX ist "verankert" dasselbe wie "absolut".
Konstruktion
Wir werden Konstruktion und Verknüpfung zusammen darstellen, da sie ähnliche Semantiken aufweisen.
Die einfachste Art, einen Pfad zu konstruieren, ist, ihm seine String-Darstellung zu übergeben
>>> PurePath('setup.py')
PurePosixPath('setup.py')
Überflüssige Pfadtrennzeichen und "."-Komponenten werden eliminiert
>>> PurePath('a///b/c/./d/')
PurePosixPath('a/b/c/d')
Wenn Sie mehrere Argumente übergeben, werden diese automatisch verkettet
>>> PurePath('docs', 'Makefile')
PurePosixPath('docs/Makefile')
Die Semantik der Verknüpfung ähnelt os.path.join, insofern als verankerte Pfade die Informationen der zuvor verknüpften Komponenten ignorieren
>>> PurePath('/etc', '/usr', 'bin')
PurePosixPath('/usr/bin')
Bei Windows-Pfaden wird jedoch der Laufwerksbuchstabe bei Bedarf beibehalten
>>> PureWindowsPath('c:/foo', '/Windows')
PureWindowsPath('c:/Windows')
>>> PureWindowsPath('c:/foo', 'd:')
PureWindowsPath('d:')
Außerdem werden Pfadtrennzeichen auf die plattformspezifischen Standardwerte normalisiert
>>> PureWindowsPath('a/b') == PureWindowsPath('a\\b')
True
Überflüssige Pfadtrennzeichen und "."-Komponenten werden eliminiert, aber nicht ".." Komponenten
>>> PurePosixPath('a//b/./c/')
PurePosixPath('a/b/c')
>>> PurePosixPath('a/../b')
PurePosixPath('a/../b')
Mehrere führende Schrägstriche werden je nach Pfad-Flavor unterschiedlich behandelt. Sie werden unter Windows-Pfaden immer beibehalten (wegen der UNC-Notation)
>>> PureWindowsPath('//some/path')
PureWindowsPath('//some/path/')
Unter POSIX werden sie reduziert, es sei denn, es gibt genau zwei führende Schrägstriche, was ein Sonderfall in der POSIX-Spezifikation zur Pfadnamensauflösung ist (dies ist auch für die Cygwin-Kompatibilität notwendig).
>>> PurePosixPath('///some/path')
PurePosixPath('/some/path')
>>> PurePosixPath('//some/path')
PurePosixPath('//some/path')
Das Aufrufen des Konstruktors ohne Argument erstellt ein Pfadobjekt, das auf das logische "aktuelle Verzeichnis" zeigt (ohne dessen absoluten Pfad nachzuschlagen, was die Aufgabe der Klassenmethode cwd() bei konkreten Pfaden ist).
>>> PurePosixPath()
PurePosixPath('.')
Repräsentation
Um einen Pfad darzustellen (z. B. um ihn an Drittanbieterbibliotheken zu übergeben), rufen Sie einfach str() darauf auf
>>> p = PurePath('/home/antoine/pathlib/setup.py')
>>> str(p)
'/home/antoine/pathlib/setup.py'
>>> p = PureWindowsPath('c:/windows')
>>> str(p)
'c:\\windows'
Um die String-Darstellung mit Schrägstrichen zu erzwingen, verwenden Sie die Methode as_posix()
>>> p.as_posix()
'c:/windows'
Um die Byte-Darstellung zu erhalten (die unter Unix-Systemen nützlich sein kann), rufen Sie bytes() darauf auf, was intern os.fsencode() verwendet
>>> bytes(p)
b'/home/antoine/pathlib/setup.py'
Um den Pfad als file:-URI darzustellen, rufen Sie die Methode as_uri() auf
>>> p = PurePosixPath('/etc/passwd')
>>> p.as_uri()
'file:///etc/passwd'
>>> p = PureWindowsPath('c:/Windows')
>>> p.as_uri()
'file:///c:/Windows'
Das repr() eines Pfades verwendet immer Schrägstriche, auch unter Windows, zur besseren Lesbarkeit und um Benutzer daran zu erinnern, dass Schrägstriche in Ordnung sind
>>> p = PureWindowsPath('c:/Windows')
>>> p
PureWindowsPath('c:/Windows')
Eigenschaften
Mehrere einfache Eigenschaften werden für jeden Pfad bereitgestellt (jede kann leer sein)
>>> p = PureWindowsPath('c:/Downloads/pathlib.tar.gz')
>>> p.drive
'c:'
>>> p.root
'\\'
>>> p.anchor
'c:\\'
>>> p.name
'pathlib.tar.gz'
>>> p.stem
'pathlib.tar'
>>> p.suffix
'.gz'
>>> p.suffixes
['.tar', '.gz']
Ableitung neuer Pfade
Verknüpfung
Ein Pfad kann mit einem anderen mithilfe des Operators / verknüpft werden
>>> p = PurePosixPath('foo')
>>> p / 'bar'
PurePosixPath('foo/bar')
>>> p / PurePosixPath('bar')
PurePosixPath('foo/bar')
>>> 'bar' / p
PurePosixPath('bar/foo')
Wie beim Konstruktor können mehrere Pfadkomponenten angegeben werden, entweder zusammengefasst oder separat
>>> p / 'bar/xyzzy'
PurePosixPath('foo/bar/xyzzy')
>>> p / 'bar' / 'xyzzy'
PurePosixPath('foo/bar/xyzzy')
Eine joinpath()-Methode wird ebenfalls bereitgestellt, mit gleichem Verhalten
>>> p.joinpath('Python')
PurePosixPath('foo/Python')
Änderung der letzten Komponente des Pfads
Die Methode with_name() gibt einen neuen Pfad zurück, bei dem der Name geändert wurde
>>> p = PureWindowsPath('c:/Downloads/pathlib.tar.gz')
>>> p.with_name('setup.py')
PureWindowsPath('c:/Downloads/setup.py')
Sie gibt einen ValueError aus, wenn der Pfad keinen tatsächlichen Namen hat
>>> p = PureWindowsPath('c:/')
>>> p.with_name('setup.py')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "pathlib.py", line 875, in with_name
raise ValueError("%r has an empty name" % (self,))
ValueError: PureWindowsPath('c:/') has an empty name
>>> p.name
''
Die Methode with_suffix() gibt einen neuen Pfad mit geändertem Suffix zurück. Wenn der Pfad jedoch kein Suffix hat, wird das neue Suffix hinzugefügt
>>> p = PureWindowsPath('c:/Downloads/pathlib.tar.gz')
>>> p.with_suffix('.bz2')
PureWindowsPath('c:/Downloads/pathlib.tar.bz2')
>>> p = PureWindowsPath('README')
>>> p.with_suffix('.bz2')
PureWindowsPath('README.bz2')
Pfad relativ machen
Die Methode relative_to() berechnet die relative Differenz eines Pfades zu einem anderen
>>> PurePosixPath('/usr/bin/python').relative_to('/usr')
PurePosixPath('bin/python')
Ein ValueError wird ausgelöst, wenn die Methode keinen sinnvollen Wert zurückgeben kann
>>> PurePosixPath('/usr/bin/python').relative_to('/etc')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "pathlib.py", line 926, in relative_to
.format(str(self), str(formatted)))
ValueError: '/usr/bin/python' does not start with '/etc'
Sequenzähnlicher Zugriff
Die Eigenschaft parts gibt ein Tupel zurück, das schreibgeschützten sequenzähnlichen Zugriff auf die Komponenten eines Pfades ermöglicht
>>> p = PurePosixPath('/etc/init.d')
>>> p.parts
('/', 'etc', 'init.d')
Windows-Pfade behandeln den Laufwerksbuchstaben und das Root-Verzeichnis als eine einzige Pfadkomponente
>>> p = PureWindowsPath('c:/setup.py')
>>> p.parts
('c:\\', 'setup.py')
(die Trennung wäre falsch, da C: nicht das Elternverzeichnis von C:\\ ist).
Die Eigenschaft parent gibt das logische Elternverzeichnis des Pfades zurück
>>> p = PureWindowsPath('c:/python33/bin/python.exe')
>>> p.parent
PureWindowsPath('c:/python33/bin')
Die Eigenschaft parents gibt eine unveränderliche Sequenz der logischen Ahnen des Pfades zurück
>>> p = PureWindowsPath('c:/python33/bin/python.exe')
>>> len(p.parents)
3
>>> p.parents[0]
PureWindowsPath('c:/python33/bin')
>>> p.parents[1]
PureWindowsPath('c:/python33')
>>> p.parents[2]
PureWindowsPath('c:/')
Abfragen
is_relative() gibt True zurück, wenn der Pfad relativ ist (siehe obige Definition), andernfalls False.
is_reserved() gibt True zurück, wenn ein Windows-Pfad ein reservierter Pfad wie CON oder NUL ist. Für POSIX-Pfade gibt es immer False zurück.
match() gleicht den Pfad mit einem Glob-Muster ab. Es operiert auf einzelnen Komponenten und gleicht von rechts ab
>>> p = PurePosixPath('/usr/bin')
>>> p.match('/usr/b*')
True
>>> p.match('usr/b*')
True
>>> p.match('b*')
True
>>> p.match('/u*')
False
Dieses Verhalten respektiert die folgenden Erwartungen
- Ein einfaches Muster wie "*.py" gleicht beliebig lange Pfade ab, solange die letzte Komponente übereinstimmt, z. B. "/usr/foo/bar.py".
- Längere Muster können für komplexere Übereinstimmungen verwendet werden, z. B. "/usr/foo/*.py" gleicht "/usr/foo/bar.py".
Concrete Paths API
Zusätzlich zu den Operationen der reinen API bieten konkrete Pfade zusätzliche Methoden, die tatsächlich auf das Dateisystem zugreifen, um Informationen abzufragen oder zu ändern.
Konstruktion
Die Klassenmethode cwd() erstellt ein Pfadobjekt, das auf das aktuelle Arbeitsverzeichnis in absoluter Form zeigt
>>> Path.cwd()
PosixPath('/home/antoine/pathlib')
Datei-Metadaten
Die Methode stat() gibt das stat()-Ergebnis der Datei zurück; ebenso gibt lstat() das lstat()-Ergebnis der Datei zurück (was sich nur unterscheidet, wenn die Datei ein symbolischer Link ist)
>>> p.stat()
posix.stat_result(st_mode=33277, st_ino=7483155, st_dev=2053, st_nlink=1, st_uid=500, st_gid=500, st_size=928, st_atime=1343597970, st_mtime=1328287308, st_ctime=1343597964)
Höherrangige Methoden helfen bei der Untersuchung der Art der Datei
>>> p.exists()
True
>>> p.is_file()
True
>>> p.is_dir()
False
>>> p.is_symlink()
False
>>> p.is_socket()
False
>>> p.is_fifo()
False
>>> p.is_block_device()
False
>>> p.is_char_device()
False
Die Namen des Dateibesitzers und der Gruppe (anstelle von numerischen IDs) werden über entsprechende Methoden abgefragt
>>> p = Path('/etc/shadow')
>>> p.owner()
'root'
>>> p.group()
'shadow'
Pfadauflösung
Die Methode resolve() macht einen Pfad absolut und löst dabei alle Symlinks auf (ähnlich dem POSIX realpath()-Aufruf). Sie ist die einzige Operation, die ".." Pfadkomponenten entfernt. Unter Windows kümmert sich diese Methode auch darum, den kanonischen Pfad (mit der richtigen Groß-/Kleinschreibung) zurückzugeben.
Verzeichnis-Durchlauf
Einfacher (nicht rekursiver) Verzeichniszugriff erfolgt durch Aufrufen der Methode iterdir(), die einen Iterator über die Kindpfade zurückgibt
>>> p = Path('docs')
>>> for child in p.iterdir(): child
...
PosixPath('docs/conf.py')
PosixPath('docs/_templates')
PosixPath('docs/make.bat')
PosixPath('docs/index.rst')
PosixPath('docs/_build')
PosixPath('docs/_static')
PosixPath('docs/Makefile')
Dies ermöglicht einfaches Filtern durch Listen-Komprehensionen
>>> p = Path('.')
>>> [child for child in p.iterdir() if child.is_dir()]
[PosixPath('.hg'), PosixPath('docs'), PosixPath('dist'), PosixPath('__pycache__'), PosixPath('build')]
Einfaches und rekursives Globbing wird ebenfalls bereitgestellt
>>> for child in p.glob('**/*.py'): child
...
PosixPath('test_pathlib.py')
PosixPath('setup.py')
PosixPath('pathlib.py')
PosixPath('docs/conf.py')
PosixPath('build/lib/pathlib.py')
Datei-Öffnung
Die Methode open() bietet eine Dateipfad-Öffnungs-API, die der eingebauten open()-Methode ähnelt
>>> p = Path('setup.py')
>>> with p.open() as f: f.readline()
...
'#!/usr/bin/env python3\n'
Dateisystemänderung
Mehrere gängige Dateisystemoperationen werden als Methoden bereitgestellt: touch(), mkdir(), rename(), replace(), unlink(), rmdir(), chmod(), lchmod(), symlink_to(). Weitere Operationen könnten bereitgestellt werden, zum Beispiel einige der Funktionalitäten des shutil-Moduls.
Eine detaillierte Dokumentation der vorgeschlagenen API finden Sie unter den pathlib-Dokumenten.
Diskussion
Divisionsoperator
Der Divisionsoperator erschien zuerst in einer Umfrage über den Pfadverknüpfungsoperator. Erste Versionen von pathlib verwendeten stattdessen eckige Klammern (d. h. __getitem__).
joinpath()
Die Methode joinpath() hieß ursprünglich join(), aber mehrere Personen widersprachen, da sie mit str.join() verwechselt werden könnte, die andere Semantik hat. Daher wurde sie in joinpath() umbenannt.
Groß-/Kleinschreibung
Windows-Benutzer betrachten Dateisystempfade als groß-/kleinschreibungsunempfindlich und erwarten, dass Pfadobjekte diese Eigenschaft beachten, obwohl in einigen seltenen Fällen einige fremde Dateisystem-Mounts unter Windows groß-/kleinschreibungsanfällig sein können.
Mit den Worten eines Kommentators,
„Wenn glob(”*.py”) unter Windows SETUP.PY nicht findet, wäre das eine Benutzerfreundlichkeitskatastrophe.“—Paul Moore in https://mail.python.org/pipermail/python-dev/2013-April/125254.html
Urheberrecht
Dieses Dokument wurde in den öffentlichen Bereich gestellt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0428.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT