PEP 3116 – Neues I/O
- Autor:
- Daniel Stutzbach <daniel at stutzbachenterprises.com>, Guido van Rossum <guido at python.org>, Mike Verdone <mike.verdone at gmail.com>
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 26. Februar 2007
- Python-Version:
- 3.0
- Post-History:
- 26. Februar 2007
Begründung und Ziele
Python ermöglicht eine Vielzahl von stream-ähnlichen (auch datei-ähnlichen) Objekten, die über read()- und write()-Aufrufe verwendet werden können. Alles, was read() und write() bereitstellt, ist stream-ähnlich. Jedoch sind exotischere und äußerst nützliche Funktionen wie readline() oder seek() möglicherweise nicht auf jedem stream-ähnlichen Objekt verfügbar. Python benötigt eine Spezifikation für grundlegende Byte-basierte I/O-Streams, zu denen wir Pufferungs- und Textbehandlungsfunktionen hinzufügen können.
Sobald wir eine definierte rohe Byte-basierte I/O-Schnittstelle haben, können wir Pufferungs- und Textbehandlungsschichten auf jeder Byte-basierten I/O-Klasse hinzufügen. Die gleiche Pufferungs- und Textbehandlungslogik kann für Dateien, Sockets, Byte-Arrays oder benutzerdefinierte I/O-Klassen, die von Python-Programmierern entwickelt wurden, verwendet werden. Die Entwicklung einer Standarddefinition eines Streams ermöglicht es uns, Stream-basierte Operationen wie read() und write() von implementierungsspezifischen Operationen wie fileno() und isatty() zu trennen. Dies ermutigt Programmierer, Code zu schreiben, der Streams als Streams verwendet und nicht verlangt, dass alle Streams dateispezifische oder sockel-spezifische Operationen unterstützen.
Die neue I/O-Spezifikation ist ähnlich den Java I/O-Bibliotheken gedacht, aber generell weniger verwirrend. Programmierer, die sich nicht mit der neuen I/O-Welt auseinandersetzen wollen, können erwarten, dass die Factory-Methode open() ein Objekt zurückgibt, das abwärtskompatibel zu alten Dateiobjekten ist.
Spezifikation
Die Python I/O-Bibliothek wird aus drei Schichten bestehen: einer rohen I/O-Schicht, einer gepufferten I/O-Schicht und einer Text-I/O-Schicht. Jede Schicht wird durch eine abstrakte Basisklasse definiert, die mehrere Implementierungen haben kann. Die rohen I/O- und gepufferten I/O-Schichten arbeiten mit Byte-Einheiten, während die Text-I/O-Schicht mit Zeichen-Einheiten arbeitet.
Rohes I/O
Die abstrakte Basisklasse für rohes I/O ist `RawIOBase`. Sie hat mehrere Methoden, die Wrapper um die entsprechenden Betriebssystemaufrufe sind. Wenn eine dieser Funktionen für das Objekt keinen Sinn ergibt, muss die Implementierung eine `IOError`-Ausnahme auslösen. Wenn beispielsweise eine Datei schreibgeschützt geöffnet ist, löst die Methode `.write()` eine `IOError` aus. Als weiteres Beispiel, wenn das Objekt einen Socket darstellt, lösen `.seek()`, `.tell()` und `.truncate()` eine `IOError` aus. Im Allgemeinen entspricht ein Aufruf einer dieser Funktionen genau einem Betriebssystemaufruf.
.read(n: int) -> bytesLiest bis zunBytes aus dem Objekt und gibt diese zurück. Weniger alsnBytes können zurückgegeben werden, wenn der Betriebssystemaufruf weniger alsnBytes zurückgibt. Wenn 0 Bytes zurückgegeben werden, zeigt dies das Ende der Datei an. Wenn das Objekt im nicht-blockierenden Modus ist und keine Bytes verfügbar sind, gibt der AufrufNonezurück.
.readinto(b: bytes) -> intLiest bis zulen(b)Bytes aus dem Objekt und speichert sie inb, wobei die Anzahl der gelesenen Bytes zurückgegeben wird. Ähnlich wie bei `.read()` können weniger alslen(b)Bytes gelesen werden, und 0 zeigt das Ende der Datei an.Nonewird zurückgegeben, wenn ein nicht-blockierendes Objekt keine Bytes verfügbar hat. Die Länge vonbwird nie geändert.
.write(b: bytes) -> intGibt die Anzahl der geschriebenen Bytes zurück, die kleiner sein kann alslen(b).
.seek(pos: int, whence: int = 0) -> int
.tell() -> int
.truncate(n: int = None) -> int
.close() -> None
Zusätzlich definiert sie einige andere Methoden
.readable() -> boolGibtTruezurück, wenn das Objekt zum Lesen geöffnet wurde, andernfallsFalse. WennFalse, löst `.read()` eineIOErroraus, wenn es aufgerufen wird.
.writable() -> boolGibtTruezurück, wenn das Objekt zum Schreiben geöffnet wurde, andernfallsFalse. WennFalse, lösen `.write()` und `.truncate()` eineIOErroraus, wenn sie aufgerufen werden.
.seekable() -> boolGibtTruezurück, wenn das Objekt zufälligen Zugriff unterstützt (wie Festplattendateien), oderFalse, wenn das Objekt nur sequenziellen Zugriff unterstützt (wie Sockets, Pipes und TTYs). WennFalse, lösen `.seek()`, `.tell()` und `.truncate()` eineIOErroraus, wenn sie aufgerufen werden.
.__enter__() -> ContextManagerKontextmanagement-Protokoll. Gibtselfzurück.
.__exit__(...) -> NoneKontextmanagement-Protokoll. Gleicht `.close()`.
Wenn und nur wenn eine RawIOBase-Implementierung mit einem zugrunde liegenden Dateideskriptor arbeitet, muss sie zusätzlich eine `.fileno()`-Mitgliedsfunktion bereitstellen. Diese könnte spezifisch von der Implementierung definiert werden, oder eine Mix-in-Klasse könnte verwendet werden (muss darüber entschieden werden).
.fileno() -> intGibt den zugrunde liegenden Dateideskriptor zurück (eine Ganzzahl).
Anfänglich werden drei Implementierungen bereitgestellt, die die RawIOBase-Schnittstelle implementieren: FileIO, SocketIO (im Socket-Modul) und ByteIO. Jede Implementierung muss bestimmen, ob das Objekt zufälligen Zugriff unterstützt, da die vom Benutzer bereitgestellten Informationen möglicherweise nicht ausreichen (denken Sie an open("/dev/tty", "rw") oder open("/tmp/named-pipe", "rw")). Als Beispiel kann FileIO dies durch Aufruf des Systemaufrufs seek() ermitteln; wenn dieser einen Fehler zurückgibt, unterstützt das Objekt keinen zufälligen Zugriff. Jede Implementierung kann zusätzliche, für ihren Typ geeignete Methoden bereitstellen. Das ByteIO-Objekt ist analog zur cStringIO-Bibliothek von Python 2, arbeitet aber mit dem neuen Byte-Typ anstelle von Zeichenketten.
Gepuffertes I/O
Die nächste Schicht ist die gepufferte I/O-Schicht, die einen effizienteren Zugriff auf datei-ähnliche Objekte bietet. Die abstrakte Basisklasse für alle gepufferten I/O-Implementierungen ist BufferedIOBase, die ähnliche Methoden wie `RawIOBase` bereitstellt.
.read(n: int = -1) -> bytesGibt die nächstennBytes aus dem Objekt zurück. Sie kann weniger alsnBytes zurückgeben, wenn das Ende der Datei erreicht ist oder das Objekt nicht blockiert. 0 Bytes bedeuten das Ende der Datei. Diese Methode kann mehrere Aufrufe vonRawIOBase.read()durchführen, um die Bytes zu sammeln, oder keine Aufrufe vonRawIOBase.read()machen, wenn alle benötigten Bytes bereits gepuffert sind.
.readinto(b: bytes) -> int
.write(b: bytes) -> intSchreibtbBytes in den Puffer. Die Bytes werden nicht garantiert sofort an das Raw I/O-Objekt geschrieben; sie können gepuffert werden. Gibtlen(b)zurück.
.seek(pos: int, whence: int = 0) -> int
.tell() -> int
.truncate(pos: int = None) -> int
.flush() -> None
.close() -> None
.readable() -> bool
.writable() -> bool
.seekable() -> bool
.__enter__() -> ContextManager
.__exit__(...) -> None
Zusätzlich bietet die abstrakte Basisklasse eine Mitgliedsvariable
.rawEine Referenz auf das zugrunde liegendeRawIOBase-Objekt.
Die Methodensignaturen von BufferedIOBase sind größtenteils identisch mit denen von RawIOBase (Ausnahmen: `.write()` gibt `None` zurück, das Argument von `.read()` ist optional), können aber andere Semantiken haben. Insbesondere können BufferedIOBase-Implementierungen mehr Daten lesen als angefordert oder das Schreiben von Daten verzögern, indem sie Puffer verwenden. Für die meisten Zwecke ist dies für den Benutzer transparent (es sei denn, er öffnet beispielsweise dieselbe Datei über einen anderen Deskriptor). Darüber hinaus können rohe Lesevorgänge ohne besonderen Grund einen kurzen Lesevorgang zurückgeben; gepufferte Lesevorgänge geben einen kurzen Lesevorgang nur zurück, wenn das EOF erreicht ist; und rohe Schreibvorgänge können einen kurzen Zählwert zurückgeben (auch wenn nicht-blockierendes I/O nicht aktiviert ist!), während gepufferte Schreibvorgänge IOError auslösen, wenn nicht alle Bytes geschrieben oder gepuffert werden konnten.
Es gibt vier Implementierungen der BufferedIOBase-abstrakten Basisklasse, die unten beschrieben werden.
BufferedReader
Die Implementierung BufferedReader ist für sequenziell zugreifbare, nur-lesbare Objekte gedacht. Ihre `.flush()`-Methode ist eine No-Op.
BufferedWriter
Die Implementierung BufferedWriter ist für sequenziell zugreifbare, nur-schreibbare Objekte gedacht. Ihre `.flush()`-Methode erzwingt, dass alle zwischengespeicherten Daten an das zugrunde liegende `RawIOBase`-Objekt geschrieben werden.
BufferedRWPair
Die Implementierung BufferedRWPair ist für sequenziell zugreifbare Lese-/Schreibobjekte wie Sockets und TTYs gedacht. Da die Lese- und Schreibströme dieser Objekte vollständig unabhängig sind, könnte sie einfach durch die Einbeziehung einer BufferedReader- und einer BufferedWriter-Instanz implementiert werden. Sie bietet eine `.flush()`-Methode, die dieselbe Semantik hat wie die `.flush()`-Methode eines BufferedWriter.
BufferedRandom
Die Implementierung BufferedRandom ist für alle Objekte mit zufälligem Zugriff gedacht, unabhängig davon, ob sie nur lesbar, nur schreibbar oder lesbar/schreibbar sind. Im Vergleich zu den vorherigen Klassen, die auf sequenziell zugreifbaren Objekten arbeiten, muss die Klasse BufferedRandom damit umgehen, dass der Benutzer `.seek()` aufruft, um den Stream neu zu positionieren. Daher muss eine Instanz von BufferedRandom sowohl die logische als auch die tatsächliche Position innerhalb des Objekts verfolgen. Sie bietet eine `.flush()`-Methode, die alle zwischengespeicherten Schreibdaten an das zugrunde liegende RawIOBase-Objekt erzwingt und alle zwischengespeicherten Lesedaten verwirft (damit zukünftige Lesevorgänge gezwungen sind, auf die Festplatte zurückzugreifen).
F: Möchten wir in der Spezifikation vorschreiben, dass ein Wechsel zwischen Lesen und Schreiben auf einem Lese-/Schreibobjekt ein `.flush()` impliziert? Oder ist das ein Implementierungs-Convenience, auf das sich Benutzer nicht verlassen sollten?
Für ein nur-lesbares BufferedRandom-Objekt gibt `.writable()` False zurück und die Methoden `.write()` und `.truncate()` lösen IOError aus.
Für ein nur-schreibbares BufferedRandom-Objekt gibt `.readable()` False zurück und die Methode `.read()` löst IOError aus.
Text-I/O
Die Text-I/O-Schicht bietet Funktionen zum Lesen und Schreiben von Zeichenketten aus Streams. Einige neue Funktionen umfassen universelle Zeilenumbrüche sowie Zeichenkodierung und -dekodierung. Die Text-I/O-Schicht wird durch eine abstrakte Basisklasse TextIOBase definiert. Sie bietet mehrere Methoden, die den Methoden von BufferedIOBase ähneln, aber auf Zeichenbasis anstelle von Byte-Basis arbeiten. Diese Methoden sind:
.read(n: int = -1) -> str
.write(s: str) -> int
.tell() -> objectGibt einen Cookie zurück, der die aktuelle Dateiposition beschreibt. Der einzige unterstützte Verwendungszweck für den Cookie ist mit `.seek()` mit `whence` auf 0 gesetzt (d.h. absoluter Sprung).
.seek(pos: object, whence: int = 0) -> intSpringt zur Positionpos. Wennposnicht Null ist, muss es ein Cookie sein, der von `.tell()` zurückgegeben wurde, undwhencemuss Null sein.
.truncate(pos: object = None) -> intÄhnlich wieBufferedIOBase.truncate(), mit der Ausnahme, dasspos(wenn nichtNone) ein Cookie sein muss, der zuvor von `.tell()` zurückgegeben wurde.
Im Gegensatz zu rohem I/O sind die Einheiten für `.seek()` nicht spezifiziert – einige Implementierungen (z.B. StringIO) verwenden Zeichen und andere (z.B. TextIOWrapper) verwenden Bytes. Der Sonderfall für Null erlaubt es, zum Anfang oder Ende eines Streams zu gehen, ohne ein vorheriges `.tell()`. Eine Implementierung könnte den Stream-Encoder-Status in den von `.tell()` zurückgegebenen Cookie aufnehmen.
TextIOBase-Implementierungen stellen außerdem mehrere Methoden bereit, die Weiterleitungen an die zugrunde liegenden BufferedIOBase-Objekte sind.
.flush() -> None
.close() -> None
.readable() -> bool
.writable() -> bool
.seekable() -> bool
TextIOBase-Klassenimplementierungen stellen zusätzlich die folgenden Methoden bereit:
.readline() -> strLiest bis zum Zeilenumbruch oder EOF und gibt die Zeile zurück, oder"", wenn EOF sofort erreicht wird.
.__iter__() -> IteratorGibt einen Iterator zurück, der Zeilen aus der Datei liefert (was zufälligselfist).
.next() -> strGleichbedeutend mit `readline()`, außer dass `StopIteration` ausgelöst wird, wenn EOF sofort erreicht wird.
Zwei Implementierungen werden von der Python-Bibliothek bereitgestellt. Die Hauptimplementierung, TextIOWrapper, umschließt ein gepuffertes I/O-Objekt. Jedes TextIOWrapper-Objekt hat eine Eigenschaft namens ".buffer", die eine Referenz auf das zugrunde liegende BufferedIOBase-Objekt bereitstellt. Seine Initialisierungssignatur lautet wie folgt:
.__init__(self, buffer, encoding=None, errors=None, newline=None, line_buffering=False)bufferist eine Referenz auf dasBufferedIOBase-Objekt, das mitTextIOWrapperumschlossen werden soll.
encodingbezieht sich auf eine Kodierung, die zur Übersetzung zwischen der Byte-Darstellung und der Zeichen-Darstellung verwendet wird. Wenn sieNoneist, wird die Systemeinstellung der Locale als Standard verwendet.
errorsist eine optionale Zeichenkette, die die Fehlerbehandlung angibt. Sie kann gesetzt werden, wennencodinggesetzt werden kann. Standardmäßig ist sie'strict'.
newlinekannNone,'','\n','\r'oder'\r\n'sein; alle anderen Werte sind ungültig. Sie steuert die Handhabung von Zeilenenden. Sie funktioniert wie folgt:
- Beim Lesen, wenn
newlineNoneist, ist der universelle Zeilenumbruchmodus aktiviert. Zeilen im Eingabetext können mit'\n','\r'oder'\r\n'enden, und diese werden in'\n'übersetzt, bevor sie an den Aufrufer zurückgegeben werden. Wenn es''ist, ist der universelle Zeilenumbruchmodus aktiviert, aber Zeilenenden werden unverändert an den Aufrufer zurückgegeben. Wenn es einen der anderen gültigen Werte hat, werden Eingabezeilen nur durch die angegebene Zeichenkette beendet, und das Zeilenende wird unverändert an den Aufrufer zurückgegeben. (Mit anderen Worten, die Übersetzung in'\n'erfolgt nur, wennnewlineNoneist.)- Beim Schreiben, wenn
newlineNoneist, werden alle geschriebenen'\n'-Zeichen in den systemweiten Standard-Zeilentrenner,os.linesep, übersetzt. Wennnewline''ist, findet keine Übersetzung statt. Wennnewlineeiner der anderen gültigen Werte ist, werden alle geschriebenen'\n'-Zeichen in die angegebene Zeichenkette übersetzt. (Beachten Sie, dass die Regeln für die Übersetzung beim Schreiben anders sind als beim Lesen.)
line_buffering, wenn aufTruegesetzt, bewirkt, dasswrite()-Aufrufe einflush()implizieren, wenn die geschriebene Zeichenkette mindestens einen'\n'- oder'\r'-Charakter enthält. Dies wird vonopen()gesetzt, wenn erkannt wird, dass der zugrunde liegende Stream ein TTY-Gerät ist, oder wenn einbuffering-Argument von1übergeben wird.Weitere Hinweise zum Parameter
newline
- Die Unterstützung für
'\r'ist für einige OSX-Anwendungen, die Dateien mit'\r'-Zeilenenden erzeugen, weiterhin erforderlich; Excel (beim Exportieren nach Text) und Adobe Illustrator EPS-Dateien sind die häufigsten Beispiele.- Wenn die Übersetzung aktiviert ist, geschieht dies unabhängig davon, welche Methode zum Lesen oder Schreiben aufgerufen wird. Zum Beispiel ergibt `f.read()` immer dasselbe Ergebnis wie `''.join(f.readlines())`.
- Wenn universelle Zeilenumbrüche ohne Übersetzung beim Lesen angefordert werden (d.h.
newline=''), und ein System-Leseaufruf einen Puffer zurückgibt, der mit'\r'endet, wird ein weiterer System-Leseaufruf durchgeführt, um festzustellen, ob dieser von'\n'gefolgt wird oder nicht. Im universellen Zeilenumbruchmodus mit Übersetzung kann der zweite System-Leseaufruf bis zum nächsten Leseaufruf verzögert werden, und wenn der nächste System-Leseaufruf einen Puffer zurückgibt, der mit'\n'beginnt, wird dieses Zeichen einfach verworfen.
Eine weitere Implementierung, StringIO, erstellt eine datei-ähnliche TextIO-Implementierung ohne zugrunde liegendes gepuffertes I/O-Objekt. Obwohl ähnliche Funktionalität durch das Umschließen eines BytesIO-Objekts in einem TextIOWrapper bereitgestellt werden könnte, ermöglicht das StringIO-Objekt eine deutlich höhere Effizienz, da es keine tatsächliche Kodierung und Dekodierung durchführen muss. Ein String-I/O-Objekt kann einfach die kodierte Zeichenkette unverändert speichern. Die __init__-Signatur des StringIO-Objekts nimmt eine optionale Zeichenkette für den Anfangswert entgegen; die Anfangsposition ist immer 0. Es unterstützt keine Kodierungen oder Zeilenumbruchübersetzungen; Sie lesen immer genau die Zeichen zurück, die Sie geschrieben haben.
Probleme bei Unicode-Kodierung/-Dekodierung
Wir sollten es ermöglichen, die Einstellungen für Kodierung und Fehlerbehandlung später zu ändern. Das Verhalten von Text-I/O-Operationen angesichts von Unicode-Problemen und -Mehrdeutigkeiten (z. B. Diakritika, Surrogates, ungültige Bytes in einer Kodierung) sollte dasselbe sein wie das der Unicode-Methoden encode()/decode(). UnicodeError kann ausgelöst werden.
Implementierungshinweis: Wir sollten in der Lage sein, einen Großteil der vom Modul codecs bereitgestellten Infrastruktur wiederzuverwenden. Wenn es nicht die genauen APIs bereitstellt, die wir benötigen, sollten wir es refaktorisieren, um eine Neuerfindung des Rads zu vermeiden.
Nicht-blockierendes I/O
Nicht-blockierendes I/O wird nur auf der Ebene des rohen I/O vollständig unterstützt. Wenn ein rohes Objekt im nicht-blockierenden Modus ist und eine Operation blockieren würde, geben `.read()` und `.readinto()` None zurück, während `.write()` 0 zurückgibt. Um ein Objekt in den nicht-blockierenden Modus zu versetzen, muss der Benutzer die Dateinummer extrahieren und dies manuell tun.
Auf den Ebenen des gepufferten I/O und des Text-I/O lösen Lese- oder Schreibfehler aufgrund einer nicht-blockierenden Bedingung eine IOError mit errno auf EAGAIN gesetzt aus.
Ursprünglich erwogen wir, das Verhalten des rohen I/O nach oben zu propagieren, aber viele Eckfälle und Probleme traten auf. Um diese Probleme zu lösen, wären erhebliche Änderungen an den Schichten für gepuffertes I/O und Text-I/O erforderlich gewesen. Was sollte beispielsweise `.flush()` bei einem gepufferten nicht-blockierenden Objekt tun? Wie würde der Benutzer das Objekt anweisen, "Schreibe so viel wie möglich aus deinem Puffer, aber blockiere nicht"? Ein nicht-blockierendes `.flush()`, das nicht unbedingt alle verfügbaren Daten leert, ist kontraintuitiv. Da nicht-blockierende und blockierende Objekte auf diesen Ebenen solch unterschiedliche Semantiken hätten, wurde vereinbart, die Bemühungen zur Kombination in einem einzigen Typ aufzugeben.
Die eingebaute Funktion open()
Die eingebaute Funktion open() wird durch den folgenden Pseudocode spezifiziert:
def open(filename, mode="r", buffering=None, *,
encoding=None, errors=None, newline=None):
assert isinstance(filename, (str, int))
assert isinstance(mode, str)
assert buffering is None or isinstance(buffering, int)
assert encoding is None or isinstance(encoding, str)
assert newline in (None, "", "\n", "\r", "\r\n")
modes = set(mode)
if modes - set("arwb+t") or len(mode) > len(modes):
raise ValueError("invalid mode: %r" % mode)
reading = "r" in modes
writing = "w" in modes
binary = "b" in modes
appending = "a" in modes
updating = "+" in modes
text = "t" in modes or not binary
if text and binary:
raise ValueError("can't have text and binary mode at once")
if reading + writing + appending > 1:
raise ValueError("can't have read/write/append mode at once")
if not (reading or writing or appending):
raise ValueError("must have exactly one of read/write/append mode")
if binary and encoding is not None:
raise ValueError("binary modes doesn't take an encoding arg")
if binary and errors is not None:
raise ValueError("binary modes doesn't take an errors arg")
if binary and newline is not None:
raise ValueError("binary modes doesn't take a newline arg")
# XXX Need to spec the signature for FileIO()
raw = FileIO(filename, mode)
line_buffering = (buffering == 1 or buffering is None and raw.isatty())
if line_buffering or buffering is None:
buffering = 8*1024 # International standard buffer size
# XXX Try setting it to fstat().st_blksize
if buffering < 0:
raise ValueError("invalid buffering size")
if buffering == 0:
if binary:
return raw
raise ValueError("can't have unbuffered text I/O")
if updating:
buffer = BufferedRandom(raw, buffering)
elif writing or appending:
buffer = BufferedWriter(raw, buffering)
else:
assert reading
buffer = BufferedReader(raw, buffering)
if binary:
return buffer
assert text
return TextIOWrapper(buffer, encoding, errors, newline, line_buffering)
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-3116.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT