PEP 355 – Path - Objektorientierte Dateisystempfade
- Autor:
- Björn Lindqvist <bjourne at gmail.com>
- Status:
- Abgelehnt
- Typ:
- Standards Track
- Erstellt:
- 24. Jan 2006
- Python-Version:
- 2.5
- Post-History:
Ablehnungsbescheid
Dieser PEP wurde (in dieser Form) abgelehnt. Die vorgeschlagene Path-Klasse ist das ultimative "Kitchen Sink"-Objekt; aber die Vorstellung, dass es besser ist, alle Funktionalitäten, die einen Pfad verwenden, als Methode einer einzelnen Klasse zu implementieren, ist ein Anti-Pattern. (Warum nicht z.B. open()? Oder execfile()?) Die Unterklasse von str zu bilden, ist eine besonders schlechte Idee; viele String-Operationen ergeben bei Pfaden keinen Sinn. Dieser PEP dümpelte vor sich hin und obwohl die Diskussion gelegentlich aufflammt, ist es Zeit, diesen PEP von seinem Leiden zu erlösen. Ein weniger abwegiger Vorschlag könnte eher Anklang finden.
Zusammenfassung
Dieser PEP beschreibt eine neue Klasse, Path, die dem os-Modul hinzugefügt werden soll, um Pfade objektorientiert zu behandeln. Die "schwache" Deprekation verschiedener verwandter Funktionen wird ebenfalls diskutiert und empfohlen.
Hintergrund
Die in diesem PEP dargelegten Ideen sind nicht neu, sondern werden in der Python-Community seit vielen Jahren diskutiert. Viele waren der Meinung, dass die API zur Manipulation von Dateipfaden, wie sie im os.path-Modul angeboten wird, unzureichend ist. Der erste Vorschlag für ein Path-Objekt wurde 2001 von Just van Rossum auf python-dev aufgeworfen [2]. Im Jahr 2003 veröffentlichte Jason Orendorff Version 1.0 des "path module", die erste öffentliche Implementierung, die Objekte zur Darstellung von Pfaden verwendete [3].
Das path-Modul wurde schnell sehr beliebt und zahlreiche Versuche wurden unternommen, das path-Modul in die Python-Standardbibliothek aufzunehmen; [4], [5], [6], [7].
Dieser PEP fasst die Ideen und Vorschläge zusammen, die von Leuten zum path-Modul geäußert wurden, und schlägt vor, eine modifizierte Version in die Standardbibliothek aufzunehmen.
Motivation
Die Handhabung von Dateisystempfaden ist in jeder Programmiersprache eine häufige Aufgabe, und in einer Hochsprache wie Python besonders häufig. Eine gute Unterstützung für diese Aufgabe ist erforderlich, denn
- Fast jedes Programm verwendet Pfade, um auf Dateien zuzugreifen. Es ist sinnvoll, dass eine Aufgabe, die so oft ausgeführt wird, so intuitiv und einfach wie möglich ausgeführt werden kann.
- Es macht Python zu einem noch besseren Ersatz für übermäßig komplizierte Shell-Skripte.
Derzeit verfügt Python über eine große Anzahl verschiedener Funktionen, die über ein halbes Dutzend Module verstreut sind, um Pfade zu behandeln. Dies macht es für Neulinge und erfahrene Entwickler schwierig, die richtige Methode zu wählen.
Die Path-Klasse bietet die folgenden Verbesserungen gegenüber der aktuellen gängigen Praxis:
- Ein "einheitliches" Objekt bietet die gesamte Funktionalität früherer Funktionen.
- Unterklassenfähigkeit - das
Path-Objekt kann erweitert werden, um andere Pfade als Dateisystempfade zu unterstützen. Der Programmierer muss keine neue API lernen, sondern kann sein Wissen über Path wiederverwenden, um die erweiterte Klasse zu behandeln. - Da alle verwandten Funktionen an einem Ort sind, ist der richtige Ansatz leichter zu erlernen, da man nicht in vielen verschiedenen Modulen nach den richtigen Funktionen suchen muss.
- Python ist eine objektorientierte Sprache. So wie Dateien, Datumsangaben und Sockets Objekte sind, sind auch Pfade Objekte, sie sind nicht nur Strings, die an Funktionen übergeben werden.
Path-Objekte sind von Natur aus eine pythonische Idee. Pathnutzt Eigenschaften (Properties) aus. Eigenschaften machen den Code lesbarer.if imgpath.ext == 'jpg': jpegdecode(imgpath)
Ist besser als
if os.path.splitexit(imgpath)[1] == 'jpg': jpegdecode(imgpath)
Begründung
Die folgenden Punkte fassen das Design zusammen:
Patherbt von String, daher muss kein Code, der String-Pfadnamen erwartet, geändert werden und kein bestehender Code wird brechen.- Ein
Path-Objekt kann entweder durch die Verwendung der KlassenmethodePath.cwd, durch die Instanziierung der Klasse mit einem String, der einen Pfad repräsentiert, oder durch die Verwendung des Standardkonstruktors, derPath(".")entspricht, erstellt werden. Pathbietet die übliche Pfadmanipulation, Mustererweiterung, Musterabgleich und andere High-Level-Dateivorgänge, einschließlich Kopieren. Im Grunde bietetPathalles Pfadbezogene außer der Manipulation von Dateiinhalten, wofür Dateiobjekte besser geeignet sind.- Plattforminkompatibilitäten werden durch die Nichtinstanziierung systemspezifischer Methoden behandelt.
Spezifikation
Diese Klasse definiert die folgende öffentliche Schnittstelle (Docstrings wurden aus der Referenzimplementierung entnommen und zur Kürze gekürzt; weitere Details finden Sie in der Referenzimplementierung).
class Path(str):
# Special Python methods:
def __new__(cls, *args) => Path
"""
Creates a new path object concatenating the *args. *args
may only contain Path objects or strings. If *args is
empty, Path(os.curdir) is created.
"""
def __repr__(self): ...
def __add__(self, more): ...
def __radd__(self, other): ...
# Alternative constructor.
def cwd(cls): ...
# Operations on path strings:
def abspath(self) => Path
"""Returns the absolute path of self as a new Path object."""
def normcase(self): ...
def normpath(self): ...
def realpath(self): ...
def expanduser(self): ...
def expandvars(self): ...
def basename(self): ...
def expand(self): ...
def splitpath(self) => (Path, str)
"""p.splitpath() -> Return (p.parent, p.name)."""
def stripext(self) => Path
"""p.stripext() -> Remove one file extension from the path."""
def splitunc(self): ... # See footnote [1]
def splitall(self): ...
def relpath(self): ...
def relpathto(self, dest): ...
# Properties about the path:
parent => Path
"""This Path's parent directory as a new path object."""
name => str
"""The name of this file or directory without the full path."""
ext => str
"""
The file extension or an empty string if Path refers to a
file without an extension or a directory.
"""
drive => str
"""
The drive specifier. Always empty on systems that don't
use drive specifiers.
"""
namebase => str
"""
The same as path.name, but with one file extension
stripped off.
"""
uncshare[1]
# Operations that return lists of paths:
def listdir(self, pattern = None): ...
def dirs(self, pattern = None): ...
def files(self, pattern = None): ...
def walk(self, pattern = None): ...
def walkdirs(self, pattern = None): ...
def walkfiles(self, pattern = None): ...
def match(self, pattern) => bool
"""Returns True if self.name matches the given pattern."""
def matchcase(self, pattern) => bool
"""
Like match() but is guaranteed to be case sensitive even
on platforms with case insensitive filesystems.
"""
def glob(self, pattern):
# Methods for retrieving information about the filesystem
# path:
def exists(self): ...
def isabs(self): ...
def isdir(self): ...
def isfile(self): ...
def islink(self): ...
def ismount(self): ...
def samefile(self, other): ... # See footnote [1]
def atime(self): ...
"""Last access time of the file."""
def mtime(self): ...
"""Last-modified time of the file."""
def ctime(self): ...
"""
Return the system's ctime which, on some systems (like
Unix) is the time of the last change, and, on others (like
Windows), is the creation time for path.
"""
def size(self): ...
def access(self, mode): ... # See footnote [1]
def stat(self): ...
def lstat(self): ...
def statvfs(self): ... # See footnote [1]
def pathconf(self, name): ... # See footnote [1]
# Methods for manipulating information about the filesystem
# path.
def utime(self, times) => None
def chmod(self, mode) => None
def chown(self, uid, gid) => None # See footnote [1]
def rename(self, new) => None
def renames(self, new) => None
# Create/delete operations on directories
def mkdir(self, mode = 0777): ...
def makedirs(self, mode = 0777): ...
def rmdir(self): ...
def removedirs(self): ...
# Modifying operations on files
def touch(self): ...
def remove(self): ...
def unlink(self): ...
# Modifying operations on links
def link(self, newpath): ...
def symlink(self, newlink): ...
def readlink(self): ...
def readlinkabs(self): ...
# High-level functions from shutil
def copyfile(self, dst): ...
def copymode(self, dst): ...
def copystat(self, dst): ...
def copy(self, dst): ...
def copy2(self, dst): ...
def copytree(self, dst, symlinks = True): ...
def move(self, dst): ...
def rmtree(self, ignore_errors = False, onerror = None): ...
# Special stuff from os
def chroot(self): ... # See footnote [1]
def startfile(self): ... # See footnote [1]
Ersetzen älterer Funktionen durch die Path-Klasse
In diesem Abschnitt bedeutet "a ==> b", dass b als Ersatz für a verwendet werden kann.
In den folgenden Beispielen wird davon ausgegangen, dass die Path-Klasse mit from path import Path importiert wurde.
- Ersetzen von
os.path.joinos.path.join(os.getcwd(), "foobar") ==> Path(Path.cwd(), "foobar") os.path.join("foo", "bar", "baz") ==> Path("foo", "bar", "baz")
- Ersetzen von
os.path.splitextfname = "Python2.4.tar.gz" os.path.splitext(fname)[1] ==> fname = Path("Python2.4.tar.gz") fname.ext
Oder wenn Sie beide Teile wünschen
fname = "Python2.4.tar.gz" base, ext = os.path.splitext(fname) ==> fname = Path("Python2.4.tar.gz") base, ext = fname.namebase, fname.extx
- Ersetzen von
glob.globlib_dir = "/lib" libs = glob.glob(os.path.join(lib_dir, "*s.o")) ==> lib_dir = Path("/lib") libs = lib_dir.files("*.so")
Deprecations
Die Einführung dieses Moduls in die Standardbibliothek erfordert die "schwache" Deprekation einer Reihe bestehender Module und Funktionen. Diese Module und Funktionen sind so weit verbreitet, dass sie nicht wirklich als veraltet (deprecated) markiert werden können, indem eine DeprecationWarning ausgegeben wird. Hier bedeutet "schwache Deprekation" nur Hinweise in der Dokumentation.
Die folgende Tabelle listet die bestehenden Funktionen auf, die als veraltet markiert werden sollten.
| Path-Methode/Eigenschaft | Veraltete Funktion |
|---|---|
| normcase() | os.path.normcase() |
| normpath() | os.path.normpath() |
| realpath() | os.path.realpath() |
| expanduser() | os.path.expanduser() |
| expandvars() | os.path.expandvars() |
| parent | os.path.dirname() |
| name | os.path.basename() |
| splitpath() | os.path.split() |
| drive | os.path.splitdrive() |
| ext | os.path.splitext() |
| splitunc() | os.path.splitunc() |
| __new__() | os.path.join(), os.curdir |
| listdir() | os.listdir() [fnmatch.filter()] |
| match() | fnmatch.fnmatch() |
| matchcase() | fnmatch.fnmatchcase() |
| glob() | glob.glob() |
| exists() | os.path.exists() |
| isabs() | os.path.isabs() |
| isdir() | os.path.isdir() |
| isfile() | os.path.isfile() |
| islink() | os.path.islink() |
| ismount() | os.path.ismount() |
| samefile() | os.path.samefile() |
| atime() | os.path.getatime() |
| ctime() | os.path.getctime() |
| mtime() | os.path.getmtime() |
| size() | os.path.getsize() |
| cwd() | os.getcwd() |
| access() | os.access() |
| stat() | os.stat() |
| lstat() | os.lstat() |
| statvfs() | os.statvfs() |
| pathconf() | os.pathconf() |
| utime() | os.utime() |
| chmod() | os.chmod() |
| chown() | os.chown() |
| rename() | os.rename() |
| renames() | os.renames() |
| mkdir() | os.mkdir() |
| makedirs() | os.makedirs() |
| rmdir() | os.rmdir() |
| removedirs() | os.removedirs() |
| remove() | os.remove() |
| unlink() | os.unlink() |
| link() | os.link() |
| symlink() | os.symlink() |
| readlink() | os.readlink() |
| chroot() | os.chroot() |
| startfile() | os.startfile() |
| copyfile() | shutil.copyfile() |
| copymode() | shutil.copymode() |
| copystat() | shutil.copystat() |
| copy() | shutil.copy() |
| copy2() | shutil.copy2() |
| copytree() | shutil.copytree() |
| move() | shutil.move() |
| rmtree() | shutil.rmtree() |
Die Path-Klasse veraltet das gesamte os.path, shutil, fnmatch und glob. Ein großer Teil von os wird ebenfalls veraltet.
Geschlossene Probleme
Einige umstrittene Punkte wurden seit dem ersten Erscheinen dieses PEP auf python-dev gelöst.
- Die Methode
__div__()wurde entfernt. Die Überladung des / (Division) Operators könnte "zu viel Magie" sein und die Pfadverkettung wie eine Division aussehen lassen. Die Methode kann später immer noch hinzugefügt werden, falls der BDFL dies wünscht. An ihrer Stelle erhielt__new__()ein*argsArgument, das sowohlPath- als auch String-Objekte akzeptiert. Die*argswerden mitos.path.join()verkettet, das zum Erstellen desPath-Objekts verwendet wird. Diese Änderungen machten die problematischejoinpath()-Methode überflüssig, die entfernt wurde. - Die Methoden und Eigenschaften
getatime()/atime,getctime()/ctime,getmtime()/mtimeundgetsize()/sizeüberschnitten sich. Diese Methoden und Eigenschaften wurden zuatime(),ctime(),mtime()undsize()zusammengeführt. Der Grund, warum es sich nicht um Eigenschaften handelt, ist die Möglichkeit, dass sie sich unerwartet ändern könnten. Das folgende Beispiel garantiert nicht immer das Bestehen der Assertion.p = Path("foobar") s = p.size() assert p.size() == s
Offene Fragen
Einige Funktionen des path-Moduls von Jason Orendorff wurden weggelassen.
- Funktionen zum Öffnen eines Pfades - besser vom integrierten
open()behandelt. - Funktionen zum Lesen und Schreiben ganzer Dateien - besser von den eigenen
read()- undwrite()-Methoden von Dateiobjekten behandelt. - Eine
chdir()-Funktion könnte eine nützliche Ergänzung sein. - Ein Deprekationsplan muss aufgestellt werden. Wie viel Funktionalität sollte
Pathimplementieren? Wie viel der bestehenden Funktionalität sollte es veraltet erklären und wann? - Der Name muss offensichtlich entweder "path" oder "Path" sein, aber wo soll er leben? In einem eigenen Modul oder in
os? - Aufgrund der Tatsache, dass
Pathentweder vonstroderunicodeerbt, sind die folgenden nicht-magischen, öffentlichen Methoden aufPath-Objekten verfügbar:capitalize(), center(), count(), decode(), encode(), endswith(), expandtabs(), find(), index(), isalnum(), isalpha(), isdigit(), islower(), isspace(), istitle(), isupper(), join(), ljust(), lower(), lstrip(), replace(), rfind(), rindex(), rjust(), rsplit(), rstrip(), split(), splitlines(), startswith(), strip(), swapcase(), title(), translate(), upper(), zfill()
Auf python-dev wurde argumentiert, ob diese Vererbung sinnvoll ist oder nicht. Die meisten Diskutanten sagten, dass die meisten String-Methoden im Kontext von Dateisystempfaden keinen Sinn ergeben - sie sind nur totes Gewicht. Die andere Position, ebenfalls auf python-dev argumentiert, ist, dass die Vererbung von String sehr bequem ist, da sie es dem Code ermöglicht, "einfach zu funktionieren" mit
Path-Objekten, ohne für sie angepasst werden zu müssen.Eines der Probleme ist, dass auf Python-Ebene kein Weg existiert, ein Objekt "string-artig genug" zu machen, sodass es an die integrierte Funktion
open()(und andere integrierte Funktionen, die einen String oder Puffer erwarten) übergeben werden kann, es sei denn, das Objekt erbt vonstroderunicode. Daher erfordert das Nicht-Erben von String Änderungen im CPython-Kern.
Die Funktionen und Module, die dieses neue Modul ersetzen soll (os.path, shutil, fnmatch, glob und Teile von os), werden voraussichtlich noch lange in zukünftigen Python-Versionen verfügbar sein, um die Abwärtskompatibilität zu gewährleisten.
Referenzimplementierung
Derzeit ist die Path-Klasse als dünner Wrapper um die Standardbibliotheksmodule fnmatch, glob, os, os.path und shutil implementiert. Die Absicht dieses PEP ist es, Funktionalität aus den oben genannten Modulen nach Path zu verschieben, während sie veraltet werden.
Für weitere Details und eine Implementierung siehe:
Beispiele
In diesem Abschnitt bedeutet "a ==> b", dass b als Ersatz für a verwendet werden kann.
- Alle Python-Dateien in einem Verzeichnis ausführbar machen
DIR = '/usr/home/guido/bin' for f in os.listdir(DIR): if f.endswith('.py'): path = os.path.join(DIR, f) os.chmod(path, 0755) ==> for f in Path('/usr/home/guido/bin').files("*.py"): f.chmod(0755)
- Emacs-Backupdateien löschen
def delete_backups(arg, dirname, names): for name in names: if name.endswith('~'): os.remove(os.path.join(dirname, name)) os.path.walk(os.environ['HOME'], delete_backups, None) ==> d = Path(os.environ['HOME']) for f in d.walkfiles('*~'): f.remove()
- Den relativen Pfad zu einer Datei finden
b = Path('/users/peter/') a = Path('/users/peter/synergy/tiki.txt') a.relpathto(b)
- Einen Pfad in Verzeichnis und Dateiname aufteilen
os.path.split("/path/to/foo/bar.txt") ==> Path("/path/to/foo/bar.txt").splitpath()
- Alle Python-Skripte im aktuellen Verzeichnisbaum auflisten
list(Path().walkfiles("*.py"))
Referenzen und Fußnoten
[1] Die Methode ist auf allen Plattformen nicht garantiert verfügbar.
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0355.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT