Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

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:


Inhaltsverzeichnis

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.
  • Path nutzt 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:

  • Path erbt 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 Klassenmethode Path.cwd, durch die Instanziierung der Klasse mit einem String, der einen Pfad repräsentiert, oder durch die Verwendung des Standardkonstruktors, der Path(".") entspricht, erstellt werden.
  • Path bietet die übliche Pfadmanipulation, Mustererweiterung, Musterabgleich und andere High-Level-Dateivorgänge, einschließlich Kopieren. Im Grunde bietet Path alles 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.join
    os.path.join(os.getcwd(), "foobar")
    ==>
    Path(Path.cwd(), "foobar")
    
    os.path.join("foo", "bar", "baz")
    ==>
    Path("foo", "bar", "baz")
    
  • Ersetzen von os.path.splitext
    fname = "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.glob
    lib_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 *args Argument, das sowohl Path- als auch String-Objekte akzeptiert. Die *args werden mit os.path.join() verkettet, das zum Erstellen des Path-Objekts verwendet wird. Diese Änderungen machten die problematische joinpath()-Methode überflüssig, die entfernt wurde.
  • Die Methoden und Eigenschaften getatime()/atime, getctime()/ctime, getmtime()/mtime und getsize()/size überschnitten sich. Diese Methoden und Eigenschaften wurden zu atime(), ctime(), mtime() und size() 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()- und write()-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 Path implementieren? 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 Path entweder von str oder unicode erbt, sind die folgenden nicht-magischen, öffentlichen Methoden auf Path-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 von str oder unicode. 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.


Quelle: https://github.com/python/peps/blob/main/peps/pep-0355.rst

Zuletzt geändert: 2025-02-01 08:59:27 GMT