PEP 400 – Deprecate codecs.StreamReader und codecs.StreamWriter
- Autor:
- Victor Stinner <vstinner at python.org>
- Status:
- Verschoben
- Typ:
- Standards Track
- Erstellt:
- 28-Mai-2011
- Python-Version:
- 3.3
Zusammenfassung
io.TextIOWrapper und codecs.StreamReaderWriter bieten die gleiche API [1]. TextIOWrapper hat mehr Features und ist schneller als StreamReaderWriter. Duplizierter Code bedeutet, dass Fehler zweimal behoben werden müssen und dass wir subtile Unterschiede zwischen den beiden Implementierungen haben könnten.
Das codecs-Modul wurde in Python 2.0 eingeführt (siehe PEP 100). Das io-Modul wurde in Python 2.6 und 3.0 eingeführt (siehe PEP 3116) und in Python 2.7 und 3.1 in C neu implementiert.
PEP Verschiebung
Die weitere Erforschung der in diesem PEP behandelten Konzepte wurde mangels eines aktuellen Vertreters, der an der Förderung der Ziele des PEP interessiert ist und Feedback sammelt und einarbeitet, sowie über ausreichend verfügbare Zeit zur effektiven Umsetzung, zurückgestellt.
Motivation
Als das Python I/O-Modell für 3.0 aktualisiert wurde, wurde das Konzept eines „Streams mit bekannter Kodierung“ in Form von io.TextIOWrapper eingeführt. Da diese Klasse für die Leistung textbasierter I/O in Python 3 kritisch ist, verfügt dieses Modul über eine optimierte C-Version, die von CPython standardmäßig verwendet wird. Viele Eckfälle bei der Handhabung von Pufferung, zustandsbehafteten Codecs und universellen Zeilenumbrüchen wurden seit der Veröffentlichung von Python 3.0 behoben.
Diese neue Schnittstelle überschneidet sich stark mit den älteren codecs.StreamReader, codecs.StreamWriter und codecs.StreamReaderWriter Schnittstellen, die Teil des ursprünglichen Designs der Codec-Schnittstelle in PEP 100 waren. Diese Schnittstellen sind nach dem Prinzip einer Kodierung mit einem zugehörigen Stream organisiert (d. h. umgekehrt zur Anordnung im io-Modul). Das ursprüngliche Design von PEP 100 erforderte, dass Codec-Autoren neben den Kernmethoden encode() und decode() geeignete StreamReader- und StreamWriter-Implementierungen bereitstellen. Dies stellt für Codec-Autoren, die diese spezialisierten Implementierungen bereitstellen, eine schwere Bürde dar, um viele der Eckfälle (siehe Anhang A) korrekt zu behandeln, die nun von io.TextIOWrapper behoben wurden. Während tiefere Integration zwischen dem Codec und dem Stream theoretisch zusätzliche Optimierungen ermöglicht, wurden diese Optimierungen in der Praxis entweder nicht durchgeführt oder die damit verbundene Code-Duplizierung bedeutet, dass die in io.TextIOWrapper behobenen Eckfälle in den verschiedenen StreamReader- und StreamWriter-Implementierungen immer noch nicht korrekt behandelt werden.
Dementsprechend schlägt dieses PEP vor, dass
- codecs.open() in Python 3.3 auf das eingebaute open() delegiert wird;
- die älteren codecs.Stream*-Schnittstellen, einschließlich der streamreader- und streamwriter-Attribute von codecs.CodecInfo, in Python 3.3 als veraltet gekennzeichnet werden.
Begründung
StreamReader und StreamWriter Probleme
- StreamReader kann keine neuen Zeilen übersetzen.
- StreamWriter unterstützt keine „Zeilenpufferung“ (flush, wenn der Eingabetext eine neue Zeile enthält).
- StreamReader-Klassen der CJK-Kodierungen (z. B. GB18030) unterstützen nur UNIX-Zeilenumbrüche (’\n’).
- StreamReader und StreamWriter sind zustandsbehaftete Codecs, exponieren aber keine Funktionen zur Steuerung ihres Zustands (getstate() oder setstate()). Jeder Codec muss Eckfälle behandeln, siehe Anhang A.
- StreamReader und StreamWriter sind sehr ähnlich zu IncrementalReader und IncrementalEncoder, einige Code sind für zustandsbehaftete Codecs dupliziert (z. B. UTF-16).
- Jeder Codec muss seine eigene StreamReader- und StreamWriter-Klasse neu implementieren, auch wenn dies trivial ist (rufe einfach den Encoder/Decoder auf).
- codecs.open(filename, “r”) erstellt ein io.TextIOWrapper-Objekt.
- Kein Codec implementiert eine optimierte Methode in StreamReader oder StreamWriter basierend auf den Besonderheiten des Codecs.
Probleme im Bug-Tracker
- Issue #5445 (2009-03-08): codecs.StreamWriter.writelines Problem, wenn ein Generator übergeben wird
- Issue #7262: (2009-11-04): codecs.open() + eol (windows)
- Issue #8260 (2010-03-29): Wenn ich codecs.open(…) verwende und f.readline() gefolgt von f.read() ein falsches Ergebnis liefert
- Issue #8630 (2010-05-05): Keepends-Parameter in codec readline(s)
- Issue #10344 (2010-11-06): codecs.readline kümmert sich nicht um Pufferung
- Issue #11461 (2011-03-10): Lesen von UTF-16 mit codecs.readline() schlägt bei Surrogat-Paaren fehl
- Issue #12446 (2011-06-30): StreamReader Readlines-Verhalten seltsam
- Issue #12508 (2011-07-06): Codecs Anomalie
- Issue #12512 (2011-07-07): Codecs: StreamWriter-Probleme mit zustandsbehafteten Codecs nach einem Seek oder mit Append-Modus
- Issue #12513 (2011-07-07): codec.StreamReaderWriter: Probleme mit verschachteltem Lese-Schreib-Vorgang
TextIOWrapper Features
- TextIOWrapper unterstützt jede Art von Zeilenumbruch, einschließlich der Übersetzung von Zeilenumbrüchen (zu UNIX-Zeilenumbrüchen) zum Lesen und Schreiben.
- TextIOWrapper verwendet Codecs' inkrementelle Encoder und Decoder wieder (keine Code-Duplizierung).
- Das io-Modul (TextIOWrapper) ist schneller als das codecs-Modul (StreamReader). Es ist in C implementiert, während codecs in Python implementiert ist.
- TextIOWrapper verfügt über einen Readahead-Algorithmus, der kleine Lesevorgänge beschleunigt: Zeichen für Zeichen oder Zeile für Zeile lesen (io ist bei diesen Operationen 10x bis 25x schneller als codecs).
- TextIOWrapper hat einen Schreibpuffer.
- TextIOWrapper.tell() ist optimiert.
- TextIOWrapper unterstützt Zufallszugriff (lesen+schreiben) mit einer einzigen Klasse, die eine Optimierung von verschachtelten Lese-Schreib-Vorgängen ermöglicht (aber keine solche Optimierung ist implementiert).
TextIOWrapper Probleme
- Issue #12215 (2011-05-30): TextIOWrapper: Probleme mit verschachteltem Lese-Schreib-Vorgang
Mögliche Verbesserungen von StreamReader und StreamWriter
Durch Hinzufügen von Codec-Zustands-Lese-/Schreibfunktionen zu den StreamReader- und StreamWriter-Klassen wird es möglich, Probleme mit zustandsbehafteten Codecs in einer Basisklasse anstelle von jeder zustandsbehafteten StreamReader- und StreamWriter-Klasse zu beheben.
Es wäre möglich, StreamReader und StreamWriter zu ändern, um sie IncrementalDecoder und IncrementalEncoder verwenden zu lassen.
Ein Codec kann Varianten implementieren, die für die spezifische Kodierung optimiert sind oder bestimmte Stream-Methoden abfangen, um Funktionalität hinzuzufügen oder die Kodierungs-/Dekodierungsleistung zu verbessern. TextIOWrapper kann solche Optimierungen nicht implementieren, aber TextIOWrapper verwendet inkrementelle Encoder und Decoder sowie Lese- und Schreibpuffer, sodass der Overhead unvollständiger Eingaben gering oder null ist.
Für andere variable Längen-Kodierungen, z. B. UTF-8, könnte noch viel mehr getan werden, da diese oft Probleme am Ende eines Lesevorgangs aufgrund fehlender Bytes haben. Die UTF-32-BE/LE-Codecs könnten einfach die Zeichenposition mit 4 multiplizieren, um die Byteposition zu erhalten.
Verwendung von StreamReader und StreamWriter
Diese Klassen werden selten direkt verwendet, sondern indirekt über codecs.open(). Sie werden in der Python 3-Standardbibliothek (außerhalb des codecs-Moduls) nicht verwendet.
Einige Projekte implementieren eigene Codecs mit StreamReader und StreamWriter, verwenden diese Klassen aber nicht.
Abwärtskompatibilität
Öffentliche API beibehalten, codecs.open
codecs.open() kann durch die eingebaute open()-Funktion ersetzt werden. open() hat eine ähnliche API, hat aber auch mehr Optionen. Beide Funktionen geben dateiähnliche Objekte zurück (gleiche API).
codecs.open() war bis Python 2.6 der einzige Weg, eine Textdatei im Unicode-Modus zu öffnen. Viele Python 2-Programme verwenden diese Funktion. Das Entfernen von codecs.open() bedeutet mehr Arbeit beim Portieren von Programmen von Python 2 auf Python 3, insbesondere bei Projekten, die die gleiche Codebasis für beide Python-Versionen verwenden (ohne das 2to3-Programm zu verwenden).
codecs.open() wird zur Abwärtskompatibilität mit Python 2 beibehalten.
StreamReader und StreamWriter als veraltet kennzeichnen
Das Instanziieren von StreamReader oder StreamWriter muss in Python 3.3 eine DeprecationWarning ausgeben. Das Definieren einer Unterklasse gibt keine DeprecationWarning aus.
codecs.open() wird so geändert, dass es die eingebaute open()-Funktion (TextIOWrapper) wiederverwendet, um Textdateien zu lesen/schreiben.
Alternativer Ansatz
Eine Alternative zur Veralterung der codecs.Stream*-Klassen ist, codecs.open() in codecs.open_stream() umzubenennen und eine neue codecs.open()-Funktion zu erstellen, die open() und somit io.TextIOWrapper wiederverwendet.
Anhang A: Probleme mit zustandsbehafteten Codecs
Es ist schwierig, einen zustandsbehafteten Codec korrekt mit einem Stream zu verwenden. Einige Fälle werden vom codecs-Modul unterstützt, während io keine bekannten Fehler mehr im Zusammenhang mit zustandsbehafteten Codecs hat. Der Hauptunterschied zwischen dem codecs- und dem io-Modul besteht darin, dass Fehler in den StreamReader- und/oder StreamWriter-Klassen jedes Codecs für das codecs-Modul behoben werden müssen, während Fehler nur einmal in io.TextIOWrapper behoben werden können. Hier sind einige Beispiele für Probleme mit zustandsbehafteten Codecs.
Zustandsbehaftete Codecs
Python unterstützt die folgenden zustandsbehafteten Codecs
- cp932
- cp949
- cp950
- euc_jis_2004
- euc_jisx2003
- euc_jp
- euc_kr
- gb18030
- gbk
- hz
- iso2022_jp
- iso2022_jp_1
- iso2022_jp_2
- iso2022_jp_2004
- iso2022_jp_3
- iso2022_jp_ext
- iso2022_kr
- shift_jis
- shift_jis_2004
- shift_jisx0213
- utf_8_sig
- utf_16
- utf_32
read und seek(0)
with open(filename, 'w', encoding='utf-16') as f:
f.write('abc')
f.write('def')
f.seek(0)
assert f.read() == 'abcdef'
f.seek(0)
assert f.read() == 'abcdef'
Die io- und codecs-Module unterstützen diesen Anwendungsfall korrekt.
seek(n)
with open(filename, 'w', encoding='utf-16') as f:
f.write('abc')
pos = f.tell()
with open(filename, 'w', encoding='utf-16') as f:
f.seek(pos)
f.write('def')
f.seek(0)
f.write('###')
with open(filename, 'r', encoding='utf-16') as f:
assert f.read() == '###def'
Das io-Modul unterstützt diesen Anwendungsfall, während codecs fehlschlägt, da es beim zweiten Schreiben ein neues BOM schreibt (Issue #12512).
Append-Modus
with open(filename, 'w', encoding='utf-16') as f:
f.write('abc')
with open(filename, 'a', encoding='utf-16') as f:
f.write('def')
with open(filename, 'r', encoding='utf-16') as f:
assert f.read() == 'abcdef'
Das io-Modul unterstützt diesen Anwendungsfall, während codecs fehlschlägt, da es beim zweiten Schreiben ein neues BOM schreibt (Issue #12512).
Links
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Fußnoten
Quelle: https://github.com/python/peps/blob/main/peps/pep-0400.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT