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

Python Enhancement Proposals

PEP 471 – os.scandir() Funktion – ein besserer und schnellerer Verzeichnisiterator

Autor:
Ben Hoyt <benhoyt at gmail.com>
BDFL-Delegate:
Victor Stinner <vstinner at python.org>
Status:
Final
Typ:
Standards Track
Erstellt:
30-Mai-2014
Python-Version:
3.5
Post-History:
27-Jun-2014, 08-Jul-2014, 14-Jul-2014

Inhaltsverzeichnis

Zusammenfassung

Diese PEP schlägt die Aufnahme einer neuen Verzeichnisiterationsfunktion, os.scandir(), in die Standardbibliothek vor. Diese neue Funktion fügt nützliche Funktionalität hinzu und erhöht die Geschwindigkeit von os.walk() um das 2- bis 20-fache (abhängig von Plattform und Dateisystem), indem Aufrufe von os.stat() in den meisten Fällen vermieden werden.

Begründung

Pythons integrierte Funktion os.walk() ist erheblich langsamer als nötig, da sie – zusätzlich zum Aufruf von os.listdir() für jedes Verzeichnis – den stat()-Systemaufruf oder GetFileAttributes() für jede Datei ausführt, um festzustellen, ob der Eintrag ein Verzeichnis ist oder nicht.

Aber die zugrunde liegenden Systemaufrufe – FindFirstFile / FindNextFile unter Windows und readdir unter POSIX-Systemen – teilen Ihnen bereits mit, ob die zurückgegebenen Dateien Verzeichnisse sind oder nicht, sodass keine weiteren Systemaufrufe erforderlich sind. Darüber hinaus geben die Windows-Systemaufrufe alle Informationen für ein stat_result-Objekt zum Verzeichniseintrag zurück, wie z. B. Dateigröße und Datum der letzten Änderung.

Kurz gesagt, Sie können die Anzahl der für eine Baumfunktion wie os.walk() erforderlichen Systemaufrufe von ungefähr 2N auf N reduzieren, wobei N die Gesamtzahl der Dateien und Verzeichnisse im Baum ist. (Und da Verzeichnisbäume normalerweise breiter als tief sind, ist es oft viel besser als das.)

In der Praxis macht das Entfernen all dieser zusätzlichen Systemaufrufe os.walk() unter **Windows etwa 8-9 Mal schneller** und unter **POSIX-Systemen etwa 2-3 Mal schneller**. Wir reden hier also nicht von Mikrooptimierungen. Weitere **Benchmarks** finden Sie hier.

In gewisser Weise sind viele Leute (siehe Python **Issue 11406**) auch an einer Version von os.listdir() interessiert, die beim Iterieren Dateinamen liefert, anstatt sie als eine große Liste zurückzugeben. Dies verbessert die Speichereffizienz beim Iterieren sehr großer Verzeichnisse.

Daher kann nicht nur ein scandir() Iteratorfunktion für den direkten Aufruf bereitgestellt werden, sondern auch die vorhandene Funktion os.walk() von Python kann erheblich beschleunigt werden.

Implementierung

Die Implementierung dieses Vorschlags wurde von Ben Hoyt (erste Version) und Tim Golden (der viel mit dem C-Erweiterungsmodul geholfen hat) geschrieben. Sie ist auf GitHub unter **benhoyt/scandir** verfügbar. (Die Implementierung kann den Aktualisierungen dieser PEP etwas hinterherhinken.)

Beachten Sie, dass dieses Modul verwendet und getestet wurde (siehe Abschnitt „Nutzung in der Praxis“ in dieser PEP), sodass es mehr als ein Proof-of-Concept ist. Es ist jedoch als Beta-Software gekennzeichnet und nicht umfassend erprobt. Es bedarf einiger Bereinigungen und gründlicherer Tests, bevor es in die Standardbibliothek aufgenommen wird, sowie einer Integration in posixmodule.c.

Details des Vorschlags

os.scandir()

Insbesondere schlägt diese PEP vor, der os-Modul in der Standardbibliothek eine einzelne Funktion, scandir, hinzuzufügen, die ein einzelnes, optionales String-Argument annimmt.

scandir(path='.') -> generator of DirEntry objects

Wie listdir ruft scandir die Verzeichnisiterationssystemaufrufe des Betriebssystems auf, um die Namen der Dateien im angegebenen path zu erhalten, aber es unterscheidet sich in zwei Punkten von listdir:

  • Anstatt reine Dateinamen-Strings zurückzugeben, gibt es leichte DirEntry-Objekte zurück, die den Dateinamen-String enthalten und einfache Methoden bereitstellen, die den Zugriff auf zusätzliche Daten ermöglichen, die das Betriebssystem möglicherweise zurückgegeben hat.
  • Es gibt einen Generator zurück anstelle einer Liste, so dass scandir als echter Iterator fungiert, anstatt die vollständige Liste sofort zurückzugeben.

scandir() liefert ein DirEntry-Objekt für jede Datei und jedes Unterverzeichnis in path. Genau wie listdir werden die Pseudo-Verzeichnisse '.' und '..' übersprungen, und die Einträge werden in systemabhängiger Reihenfolge geliefert. Jedes DirEntry-Objekt hat die folgenden Attribute und Methoden:

  • name: Der Dateiname des Eintrags, relativ zum scandir-Argument path (entspricht den Rückgabewerten von os.listdir).
  • path: Der vollständige Pfadname des Eintrags (nicht unbedingt ein absoluter Pfad) – das Äquivalent von os.path.join(scandir_path, entry.name).
  • inode(): Gibt die Inode-Nummer des Eintrags zurück. Das Ergebnis wird im DirEntry-Objekt zwischengespeichert. Verwenden Sie os.stat(entry.path, follow_symlinks=False).st_ino, um aktuelle Informationen abzurufen. Unter Unix ist kein Systemaufruf erforderlich.
  • is_dir(*, follow_symlinks=True): Ähnlich wie pathlib.Path.is_dir(), aber der Rückgabewert wird im DirEntry-Objekt zwischengespeichert; erfordert in den meisten Fällen keinen Systemaufruf; folgt symbolischen Links nicht, wenn follow_symlinks False ist.
  • is_file(*, follow_symlinks=True): Ähnlich wie pathlib.Path.is_file(), aber der Rückgabewert wird im DirEntry-Objekt zwischengespeichert; erfordert in den meisten Fällen keinen Systemaufruf; folgt symbolischen Links nicht, wenn follow_symlinks False ist.
  • is_symlink(): Ähnlich wie pathlib.Path.is_symlink(), aber der Rückgabewert wird im DirEntry-Objekt zwischengespeichert; erfordert in den meisten Fällen keinen Systemaufruf.
  • stat(*, follow_symlinks=True): Wie os.stat(), aber der Rückgabewert wird im DirEntry-Objekt zwischengespeichert; erfordert unter Windows keinen Systemaufruf (außer für Symlinks); folgt symbolischen Links nicht (wie os.lstat()), wenn follow_symlinks False ist.

Alle *Methoden* können in einigen Fällen Systemaufrufe ausführen und daher möglicherweise OSError auslösen – siehe Abschnitt „Hinweise zur Fehlerbehandlung“ für weitere Details.

Die Namen der DirEntry-Attribute und -Methoden wurden, wo immer möglich, den Namen im neuen pathlib-Modul angepasst, um die Konsistenz zu gewährleisten. Der einzige Funktionsunterschied besteht darin, dass die DirEntry-Methoden ihre Werte nach dem ersten Aufruf im Entry-Objekt zwischenspeichern.

Wie die anderen Funktionen im os-Modul akzeptiert scandir() entweder ein Bytes- oder ein String-Objekt für den Parameter path und gibt die Attribute DirEntry.name und DirEntry.path mit demselben Typ wie path zurück. Es wird jedoch *dringend empfohlen*, den String-Typ zu verwenden, da dies die plattformübergreifende Unterstützung für Unicode-Dateinamen gewährleistet. (Unter Windows sind Bytes-Dateinamen seit Python 3.3 veraltet.)

os.walk()

Als Teil dieses Vorschlags wird os.walk() ebenfalls so modifiziert, dass scandir() anstelle von listdir() und os.path.isdir() verwendet wird. Dies wird die Geschwindigkeit von os.walk() erheblich erhöhen (wie oben erwähnt, um das 2- bis 20-fache, abhängig vom System).

Beispiele

Zuerst ein sehr einfaches Beispiel für scandir(), das die Verwendung des Attributs DirEntry.name und der Methode DirEntry.is_dir() zeigt.

def subdirs(path):
    """Yield directory names not starting with '.' under given path."""
    for entry in os.scandir(path):
        if not entry.name.startswith('.') and entry.is_dir():
            yield entry.name

Diese Funktion subdirs() wird mit scandir signifikant schneller sein als os.listdir() und os.path.isdir() sowohl unter Windows als auch unter POSIX-Systemen, insbesondere bei mittelgroßen oder großen Verzeichnissen.

Oder zum Ermitteln der Gesamtgröße von Dateien in einem Verzeichnisbaum, das die Verwendung der Methode DirEntry.stat() und des Attributs DirEntry.path zeigt.

def get_tree_size(path):
    """Return total size of files in given path and subdirs."""
    total = 0
    for entry in os.scandir(path):
        if entry.is_dir(follow_symlinks=False):
            total += get_tree_size(entry.path)
        else:
            total += entry.stat(follow_symlinks=False).st_size
    return total

Dies zeigt auch die Verwendung des Parameters follow_symlinks für is_dir() – in einer rekursiven Funktion wie dieser möchten wir Links wahrscheinlich nicht folgen. (Um Links in einer rekursiven Funktion wie dieser korrekt zu folgen, bräuchten wir eine spezielle Behandlung für den Fall, dass das Folgen eines Symlinks zu einer rekursiven Schleife führt.)

Beachten Sie, dass get_tree_size() unter Windows eine enorme Geschwindigkeitssteigerung erfahren wird, da keine zusätzlichen Stat-Aufrufe erforderlich sind. Unter POSIX-Systemen werden die Größeninformationen jedoch nicht von den Verzeichnisiterationsfunktionen zurückgegeben, sodass diese Funktion dort nichts gewinnt.

Hinweise zur Zwischenspeicherung

Die DirEntry-Objekte sind relativ einfach – die Attribute name und path sind offensichtlich immer zwischengespeichert, und die Methoden is_X und stat speichern ihre Werte (sofort unter Windows über FindNextFile und beim ersten Gebrauch unter POSIX-Systemen über einen stat-Systemaufruf) und rufen sie nie wieder vom System ab.

Aus diesem Grund sind DirEntry-Objekte dazu bestimmt, verwendet und nach der Iteration verworfen zu werden, nicht in langzeitigen Datenstrukturen gespeichert und die Methoden immer wieder aufgerufen zu werden.

Wenn Entwickler ein „Aktualisierungsverhalten“ wünschen (z. B. um die Größe einer Datei zu überwachen), können sie einfach pathlib.Path-Objekte verwenden oder die regulären Funktionen os.stat() oder os.path.getsize() aufrufen, die bei jedem Aufruf frische Daten vom Betriebssystem abrufen.

Hinweise zur Fehlerbehandlung

DirEntry.is_X() und DirEntry.stat() sind explizit Methoden und keine Attribute oder Eigenschaften, um zu verdeutlichen, dass sie keine günstigen Operationen sein müssen (obwohl sie es oft sind) und dass sie einen Systemaufruf durchführen können. Infolgedessen können diese Methoden OSError auslösen.

Zum Beispiel führt DirEntry.stat() unter POSIX-basierten Systemen immer einen Systemaufruf durch, und die Methoden DirEntry.is_X() führen unter solchen Systemen einen stat()-Systemaufruf aus, wenn readdir() d_type nicht unterstützt oder einen d_type mit dem Wert DT_UNKNOWN zurückgibt, was unter bestimmten Bedingungen oder auf bestimmten Dateisystemen auftreten kann.

Oft spielt das keine Rolle – zum Beispiel fängt os.walk(), wie in der Standardbibliothek definiert, nur Fehler bei den listdir()-Aufrufen ab.

Außerdem, da das Ausnahmeverhalten der Methoden DirEntry.is_X dem von pathlib entspricht – das nur im Falle von Berechtigungen oder anderen fatalen Fehlern OSError auslöst, aber False zurückgibt, wenn der Pfad nicht existiert oder ein defekter Symlink ist –, ist es oft nicht notwendig, Fehler bei den is_X()-Aufrufen abzufangen.

Wenn ein Benutzer jedoch eine feingranulare Fehlerbehandlung benötigt, kann es wünschenswert sein, OSError um alle Methodenaufrufe abzufangen und entsprechend zu behandeln.

Unten finden Sie beispielsweise eine Version des oben gezeigten get_tree_size()-Beispiels, jedoch mit hinzugefügter feingranularer Fehlerbehandlung.

def get_tree_size(path):
    """Return total size of files in path and subdirs. If
    is_dir() or stat() fails, print an error message to stderr
    and assume zero size (for example, file has been deleted).
    """
    total = 0
    for entry in os.scandir(path):
        try:
            is_dir = entry.is_dir(follow_symlinks=False)
        except OSError as error:
            print('Error calling is_dir():', error, file=sys.stderr)
            continue
        if is_dir:
            total += get_tree_size(entry.path)
        else:
            try:
                total += entry.stat(follow_symlinks=False).st_size
            except OSError as error:
                print('Error calling stat():', error, file=sys.stderr)
    return total

Unterstützung

Das scandir-Modul auf GitHub wurde mehrfach geforkt und genutzt (siehe „Nutzung in der Praxis“ in dieser PEP), aber es gab auch erhebliche direkte Unterstützung für eine scandir-ähnliche Funktion von Kernentwicklern und anderen in den Mailinglisten python-dev und python-ideas. Eine Auswahl:

  • **python-dev**: eine gute Anzahl von +1 und sehr wenige Negative für scandir und PEP 471 in diesem python-dev-Thread vom Juni 2014
  • **Alyssa Coghlan**, eine Kern-Python-Entwicklerin: „Das lokale Release-Engineering-Team von Red Hat hat seine Unzufriedenheit darüber geäußert, dass jeder Dateieintrag in einem Netzwerklaufwerksverzeichnisbaum für Informationen, die in der dirent-Struktur vorhanden sind, statisch abgefragt werden muss. Daher ein klares +1 für os.scandir von mir, solange es diese Informationen verfügbar macht.“ [Quelle1]
  • **Tim Golden**, ein Kern-Python-Entwickler, unterstützt scandir so sehr, dass er Zeit damit verbracht hat, das C-Erweiterungsmodul von scandir zu refaktorisieren und erheblich zu verbessern. [Quelle2]
  • **Christian Heimes**, ein Kern-Python-Entwickler: „+1 für so etwas wie yielddir()“ [Quelle3] und „In der Tat! Ich würde mir wünschen, dass die Funktion in 3.4 enthalten ist, damit ich meinen eigenen Hack aus unserer Codebasis entfernen kann.“ [Quelle4]
  • **Gregory P. Smith**, ein Kern-Python-Entwickler: „Da 3.4beta1 heute Abend stattfindet, wird dies nicht in 3.4 enthalten sein. Ich verschiebe dies daher auf 3.5. Ich mag das vorgeschlagene Design, das oben skizziert ist, sehr.“ [Quelle5]
  • **Guido van Rossum** über die Möglichkeit, scandir zu Python 3.5 hinzuzufügen (da es für 3.4 zu spät war): „Das Schiff ist ebenfalls für die Aufnahme von scandir() (ob in os oder pathlib) abgefahren. Experimentieren Sie nach Belieben und bereiten Sie es für die Berücksichtigung für 3.5 vor, aber ich möchte es nicht zu 3.4 hinzufügen.“ [Quelle6]

Die Unterstützung für diese PEP selbst (Meta-Unterstützung?) kam von Alyssa (Nick) Coghlan auf python-dev: „Eine PEP, die all dies für 3.5 prüft und eine spezifische os.scandir-API vorschlägt, wäre eine gute Sache.“ [Quelle7]

Nutzung in der Praxis

Bis heute ist die scandir-Implementierung definitiv nützlich, wurde aber klar als „Beta“ gekennzeichnet, sodass es unsicher ist, wie stark sie in der Praxis genutzt wird. Ben Hoyt hat mehrere Berichte von Leuten erhalten, die sie verwenden. Zum Beispiel:

  • Chris F: „Ich verarbeite einige ziemlich große Verzeichnisse und hatte erwartet, getdents modifizieren zu müssen. Vielen Dank, dass Sie mir die Mühe erspart haben.“ [per persönlicher E-Mail]
  • bschollnick: „Ich wollte Sie darüber informieren, da ich Scandir als Baustein für diesen Code verwende. Hier ist ein gutes Beispiel dafür, wie scandir eine radikale Leistungsverbesserung gegenüber os.listdir erzielt.“ [Quelle8]
  • Avram L: „Ich teste unser scandir für ein Projekt, an dem ich arbeite. Es scheint ziemlich solide zu sein, daher möchte ich Ihnen zuerst sagen, gute Arbeit!“ [per persönlicher E-Mail]
  • Matt Z: „Ich habe scandir verwendet, um den Inhalt eines Netzwerkkatalogs in unter 15 Sekunden zu entleeren. 13 Stammverzeichnisse, 60.000 Dateien in der Struktur. Dies wird einige alte VBA-Codes ersetzen, die in einer Tabellenkalkulation eingebettet waren und 15-20 Minuten für dieselbe Aufgabe brauchten.“ [per persönlicher E-Mail]

Andere haben **ein PyPI-Paket** dafür angefordert, das auch erstellt wurde. Siehe **PyPI-Paket**.

GitHub-Statistiken sind nicht besonders aussagekräftig, aber scandir hat mehrere Beobachter, Probleme, Forks usw. Hier ist die Aufschlüsselung der Statistiken vom 7. Juli 2014:

  • Beobachter: 17
  • Sterne: 57
  • Forks: 20
  • Probleme: 4 offen, 26 geschlossen

Darüber hinaus, da diese PEP die Geschwindigkeit von os.walk() erheblich erhöht, würden Tausende von Entwicklern und Skripten sowie viel Produktionscode davon profitieren. Zum Beispiel gibt es auf GitHub fast genauso viele Verwendungen von os.walk (194.000) wie von os.mkdir (230.000).

Abgelehnte Ideen

Benennung

Der einzige andere wirkliche Anwärter für den Namen dieser Funktion war iterdir(). Funktionen vom Typ iterX() in Python (hauptsächlich in Python 2 zu finden) sind jedoch tendenziell einfache Iterator-Äquivalente ihrer Nicht-Iterator-Gegenstücke. Zum Beispiel ist dict.iterkeys() nur eine Iteratorversion von dict.keys(), aber die zurückgegebenen Objekte sind identisch. Im Fall von scandir() sind die Rückgabewerte jedoch ganz andere Objekte (DirEntry-Objekte im Gegensatz zu Dateinamen-Strings), daher sollte dies wahrscheinlich durch eine Namensänderung widergespiegelt werden – daher scandir().

Siehe einige **relevante Diskussionen auf python-dev**.

Wildcard-Unterstützung

FindFirstFile/FindNextFile unter Windows unterstützt die Übergabe eines „Wildcards“ wie *.jpg. Daher hielten es zuerst einige Leute (einschließlich des Autors dieser PEP) für eine gute Idee, ein Schlüsselwortargument windows_wildcard für die Funktion scandir einzuführen, damit Benutzer dies übergeben können.

Nach weiterer Überlegung und Diskussion wurde jedoch entschieden, dass dies eine schlechte Idee wäre, *es sei denn, es könnte plattformübergreifend umgesetzt werden* (ein Schlüsselwortargument pattern oder Ähnliches). Dies scheint zunächst einfach genug zu sein – verwenden Sie einfach die OS-Wildcard-Unterstützung unter Windows und etwas wie fnmatch oder re anschließend unter POSIX-Systemen.

Leider sind die genauen Windows-Wildcard-Matching-Regeln von Microsoft nirgends dokumentiert und sie sind ziemlich eigenartig (siehe diesen **Blogbeitrag**), was es sehr problematisch macht, sie mit fnmatch oder Regexes zu emulieren.

Daher war der Konsens, dass die Windows-Wildcard-Unterstützung eine schlechte Idee war. Sie könnte zu einem späteren Zeitpunkt hinzugefügt werden, wenn es einen plattformübergreifenden Weg gibt, sie zu erreichen, aber nicht für die Erstversion.

Lesen Sie mehr in diesem **Nov 2012 python-ideas-Thread** und diesem **Juni 2014 python-dev-Thread zu PEP 471**.

DirEntry-Attribute als Eigenschaften

In gewisser Weise wäre es schöner, wenn die DirEntry is_X() und stat() Eigenschaften anstelle von Methoden wären, um anzuzeigen, dass sie sehr günstig oder kostenlos sind. Dies ist jedoch nicht ganz der Fall, da stat() unter POSIX-basierten Systemen einen OS-Aufruf erfordert, unter Windows jedoch nicht. Selbst is_dir() und ähnliche können unter POSIX-basierten Systemen einen OS-Aufruf ausführen, wenn der dirent.d_type-Wert DT_UNKNOWN ist (auf bestimmten Dateisystemen).

Außerdem würden die Leute erwarten, dass der Attributzugriff entry.is_dir nur AttributeError auslöst, nicht OSError im Fall, dass er im Hintergrund einen Systemaufruf tätigt. Aufrufender Code müsste einen try/except um das haben, was wie ein einfacher Attributzugriff aussieht, und daher ist es viel besser, sie zu *Methoden* zu machen.

Siehe **diesen May 2013 python-dev-Thread**, in dem der Autor dieser PEP dies darlegt und eine Zustimmung von Kernentwicklern erhält.

DirEntry-Felder als „statische“ Attribute

In **dieser Juli 2014 python-dev-Nachricht** schlug Paul Moore eine Lösung vor, die ein „dünner Wrapper um das Betriebssystemmerkmal“ war, bei dem das DirEntry-Objekt nur statische Attribute hatte: name, path und is_X, wobei die st_X-Attribute nur unter Windows vorhanden waren. Die Idee war, diese einfachere, Low-Level-Funktion als Baustein für High-Level-Funktionen zu verwenden.

Anfangs stimmten die meisten zu, dass eine solche Vereinfachung gut sei. Es gab jedoch zwei Probleme mit diesem Ansatz. Erstens geht man davon aus, dass is_dir und ähnliche Attribute unter POSIX immer vorhanden sind, was nicht der Fall ist (wenn d_type nicht vorhanden ist oder DT_UNKNOWN ist). Zweitens ist es in der Praxis eine viel schwieriger zu bedienende API, da selbst die Attribute is_dir unter POSIX nicht immer vorhanden sind und mit hasattr() getestet und dann os.stat() aufgerufen werden müssten, wenn sie nicht vorhanden wären.

Siehe **diese Juli 2014 python-dev-Antwort** vom Autor dieser PEP, die detailliert beschreibt, warum diese Option keine ideale Lösung ist, und die anschließende Antwort von Paul Moore, der zustimmt.

DirEntry-Felder als statisch mit einer ensure_lstat-Option

Eine weitere scheinbar einfachere und attraktivere Option wurde von Alyssa Coghlan in dieser **Juni 2014 python-dev-Nachricht** vorgeschlagen: Machen Sie DirEntry.is_X und DirEntry.lstat_result zu Eigenschaften und füllen Sie DirEntry.lstat_result während der Iteration, aber nur, wenn das neue Argument ensure_lstat=True beim Aufruf von scandir() angegeben wurde.

Dies hat den Vorteil gegenüber dem oben genannten, dass Sie das Stat-Ergebnis leicht von scandir() erhalten können, wenn Sie es benötigen. Es hat jedoch den erheblichen Nachteil, dass die feingranulare Fehlerbehandlung unübersichtlich ist, da stat() während der Iteration aufgerufen wird (und somit möglicherweise OSError auslöst), was zu einer ziemlich hässlichen, handgemachten Iterationsschleife führt.

it = os.scandir(path)
while True:
    try:
        entry = next(it)
    except OSError as error:
        handle_error(path, error)
    except StopIteration:
        break

Oder es bedeutet, dass scandir() ein Argument onerror akzeptieren müsste – eine Funktion, die aufgerufen wird, wenn stat()-Fehler während der Iteration auftreten. Dies scheint dem Autor dieser PEP weder so direkt noch so Python-isch wie ein try/except-Block um einen DirEntry.stat()-Aufruf zu sein.

Ein weiterer Nachteil ist, dass os.scandir() zur Beschleunigung des Codes geschrieben ist. Das ständige Aufrufen von os.lstat() unter POSIX würde keine Geschwindigkeitssteigerung bringen. In den meisten Fällen benötigen Sie nicht das vollständige stat_result-Objekt – die Methoden is_X() sind ausreichend, und diese Informationen sind bereits bekannt.

Siehe **Ben Hoyts Juli 2014er Antwort** auf die Diskussion, die dies zusammenfasst und detailliert beschreibt, warum er den ursprünglichen Vorschlag von **PEP 471** doch „den richtigen“ hält.

Rückgabewerte als (Name, stat_result) Zwei-Tupel

Anfänglich schlug der Autor dieser PEP dieses Konzept als Funktion namens iterdir_stat() vor, die Zwei-Tupel von (name, stat_result) lieferte. Dies hat den Vorteil, dass keine neuen Typen eingeführt werden. Allerdings ist das stat_result unter POSIX-basierten Systemen nur teilweise gefüllt (die meisten Felder sind auf None gesetzt und es gibt andere Eigenheiten), sodass es sich nicht wirklich um stat_result-Objekte handelt, und dies müsste als anders als os.stat() gründlich dokumentiert werden.

Python unterstützt ordnungsgemäße Objekte mit Attributen und Methoden gut, was zu einer gesünderen und einfacheren API als bei Zwei-Tupeln führt. Außerdem macht es die DirEntry-Objekte erweiterbarer und zukunftssicherer, da Betriebssysteme Funktionalität hinzufügen und wir diese in DirEntry aufnehmen möchten.

Siehe auch einige frühere Diskussionen

Rückgabewerte als überladene stat_result-Objekte

Eine weitere diskutierte Alternative war, die Rückgabewerte als überladene stat_result-Objekte mit den Attributen name und path zu gestalten. Abgesehen davon, dass dies eine seltsame (und angestrengte!) Art der Überladung ist, hat dies die gleichen oben genannten Probleme – die meisten stat_result-Informationen werden auf POSIX-Systemen nicht von readdir() abgerufen, nur (ein Teil) des st_mode-Werts.

Rückgabewerte als pathlib.Path-Objekte

Mit Antoine Pitrous neuem Standardbibliothekmodul pathlib scheint es auf den ersten Blick eine großartige Idee zu sein, dass scandir() Instanzen von pathlib.Path zurückgibt. Die is_X() und stat() Funktionen von pathlib.Path sind jedoch explizit nicht zwischengespeichert, während scandir sie bauartbedingt zwischenspeichern muss, da es (oft) Werte aus dem ursprünglichen Systemaufruf zur Verzeichnisiteration zurückgibt.

Und wenn die von scandir zurückgegebenen pathlib.Path-Instanzen Stat-Werte zwischenspeichern würden, die normalen pathlib.Path-Objekte aber explizit nicht, wäre das mehr als ein wenig verwirrend.

Guido van Rossum lehnte das Zwischenspeichern von Stat-Werten durch pathlib.Path im Kontext von scandir hier ausdrücklich ab, was pathlib.Path-Objekte zu einer schlechten Wahl für scandir-Rückgabewerte macht.

Mögliche Verbesserungen

Es gibt viele mögliche Verbesserungen, die man an scandir vornehmen könnte, aber hier ist eine kurze Liste einiger, die der Autor dieses PEP im Sinn hat

  • scandir könnte potenziell weiter beschleunigt werden, indem readdir / FindNextFile sagen wir 50 Mal pro Py_BEGIN_ALLOW_THREADS-Block aufgerufen wird, damit es länger im C-Erweiterungsmodul verbleibt und dadurch möglicherweise etwas schneller ist. Dieser Ansatz wurde nicht getestet, wurde aber von Antoine Pitrou in Issue 11406 vorgeschlagen. [source9]
  • scandir könnte eine Freiliste verwenden, um die Kosten für die Speicherzuweisung für jede Iteration zu vermeiden – eine kurze Freiliste von 10 oder vielleicht sogar 1 könnte helfen. Vorgeschlagen von Victor Stinner in einer Diskussion auf python-dev am 27. Juni.

Frühere Diskussion


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

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