PEP 400 – codecs.StreamReader 및 codecs.StreamWriter의 사용을 중단합니다.
- Author:
- Victor Stinner <vstinner at python.org>
- Status:
- Deferred
- Type:
- Standards Track
- Created:
- 28-May-2011
- Python-Version:
- 3.3
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
io.TextIOWrapper와 codecs.StreamReaderWriter는 동일한 API를 제공합니다 [1]. TextIOWrapper는 StreamReaderWriter보다 더 많은 기능을 제공하며 더 빠릅니다. 코드가 중복되면 버그를 두 번 수정해야 하며 두 구현 사이에 미묘한 차이가 발생할 수 있습니다.
codecs 모듈은 Python 2.0에서 도입되었습니다(PEP 100 참조). io 모듈은 Python 2.6 및 3.0에서 도입되었으며(PEP 3116 참조), Python 2.7 및 3.1에서 C로 재구현되었습니다.
PEP 연기
이 PEP에서 다루는 개념에 대한 추가 검토는 PEP의 목표를 추진하고 피드백을 수집 및 반영하는 데 관심이 있는 현재의 담당자가 없고, 이를 효과적으로 수행할 수 있는 충분한 시간도 없기 때문에 연기되었습니다.
동기
Python 3.0을 위해 Python I/O 모델을 업데이트할 때 io.TextIOWrapper 형태로 “알려진 인코딩을 사용하는 스트림”이라는 개념이 도입되었습니다. 이 클래스는 Python 3에서 텍스트 기반 I/O의 성능에 매우 중요하므로, 이 모듈에는 최적화된 C 버전이 있으며 CPython은 기본적으로 이를 사용합니다. Python 3.0이 출시된 이후 버퍼링, 상태 유지 코덱 및 범용 줄 바꿈을 처리할 때 발생하는 많은 예외적인 경우가 해결되었습니다.
이 새로운 인터페이스는 PEP 100의 원래 코덱 인터페이스 설계에 포함된 기존 codecs.StreamReader, codecs.StreamWriter 및 codecs.StreamReaderWriter 인터페이스와 크게 겹칩니다. 이러한 인터페이스는 스트림이 연결된 인코딩이라는 원칙을 중심으로 구성됩니다(즉, io 모듈의 배열과 반대입니다). 따라서 원래의 PEP 100 설계에서는 코덱 작성자가 핵심 코덱 encode() 및 decode() 메서드와 함께 적절한 StreamReader 및 StreamWriter 구현도 제공해야 했습니다. 이로 인해 이러한 특수 구현을 제공하는 코덱 작성자는 이제 io.TextIOWrapper에서 처리되는 많은 예외적인 경우를 올바르게 처리해야 하는 큰 부담을 지게 됩니다(Appendix A을 참조하십시오). 코덱과 스트림을 더욱 긴밀하게 통합하면 이론적으로 추가 최적화가 가능하지만, 실제로 이러한 최적화가 수행되지 않았거나 관련 코드 중복으로 인해 io.TextIOWrapper에서 수정된 예외적인 경우가 다양한 StreamReader 및 StreamWriter 구현에서 여전히 올바르게 처리되지 않습니다.
따라서 이 PEP에서는 다음을 제안합니다.
- Python 3.3에서 codecs.open()이 내장 open()에 처리를 위임하도록 업데이트합니다.
- Python 3.3에서 기존 codecs.Stream* 인터페이스와 codecs.CodecInfo의 streamreader 및 streamwriter 속성을 더 이상 사용하지 않도록 합니다.
근거
StreamReader 및 StreamWriter 관련 문제
- StreamReader는 줄 바꿈을 변환할 수 없습니다.
- StreamWriter는 “줄 버퍼링”(입력 텍스트에 줄 바꿈이 포함되어 있으면 플러시)을 지원하지 않습니다.
- CJK 인코딩의 StreamReader 클래스(예: GB18030)는 UNIX 줄 바꿈(’\n’)만 지원합니다.
- StreamReader와 StreamWriter는 상태 유지 코덱이지만 상태를 제어하는 함수(getstate() 또는 setstate())를 노출하지 않습니다. 각 코덱은 예외적인 경우를 처리해야 합니다(Appendix A을 참조하십시오).
- StreamReader와 StreamWriter는 IncrementalReader 및 IncrementalEncoder와 매우 유사하며, 일부 코드는 상태 유지 코덱(예: UTF-16)에서 중복됩니다.
- 각 코덱은 사소한 경우(인코더 또는 디코더를 호출하기만 하는 경우)에도 자체 StreamReader 및 StreamWriter 클래스를 다시 구현해야 합니다.
- codecs.open(filename, “r”)은 io.TextIOWrapper 객체를 생성합니다.
- 어떤 코덱도 해당 코덱의 특성에 기반한 최적화된 StreamReader 또는 StreamWriter 메서드를 구현하지 않습니다.
버그 추적기의 문제:
- Issue #5445 (2009-03-08): 제너레이터가 전달될 때 codecs.StreamWriter.writelines의 문제
- Issue #7262: (2009-11-04): codecs.open() + eol (Windows)
- Issue #8260 (2010-03-29): codecs.open(…)을 사용하고 f.readline() 다음에 f.read()를 호출하면 잘못된 결과가 반환됩니다
- Issue #8630 (2010-05-05): codec readline(s)의 Keepends 매개변수
- Issue #10344 (2010-11-06): codecs.readline이 버퍼링을 고려하지 않습니다
- Issue #11461 (2011-03-10): codecs.readline()으로 UTF-16을 읽으면 서로게이트 쌍이 깨집니다
- Issue #12446 (2011-06-30): StreamReader Readlines 동작이 이상합니다
- Issue #12508 (2011-07-06): 코덱 이상
- Issue #12512 (2011-07-07): codecs: seek 후 또는 추가 모드에서 상태 저장 코덱을 사용할 때 StreamWriter 문제
- Issue #12513 (2011-07-07): codec.StreamReaderWriter: 인터리브된 읽기 및 쓰기 문제
TextIOWrapper 기능
- TextIOWrapper는 읽기 및 쓰기를 위해 모든 종류의 줄바꿈을 지원하며, 줄바꿈을 UNIX 줄바꿈으로 변환하는 기능도 지원합니다.
- TextIOWrapper는 codecs의 증분 인코더와 디코더를 재사용합니다(코드 중복 없음).
- io 모듈(TextIOWrapper)은 codecs 모듈(StreamReader)보다 빠릅니다. io 모듈은 C로 구현된 반면, codecs는 Python으로 구현됩니다.
- TextIOWrapper에는 작은 읽기 작업을 빠르게 하는 미리 읽기 알고리즘이 있습니다. 문자 단위 또는 줄 단위로 읽을 수 있으며, 이러한 작업에서는 io가 codecs보다 10배에서 25배 빠릅니다.
- TextIOWrapper에는 쓰기 버퍼가 있습니다.
- TextIOWrapper.tell()은 최적화되어 있습니다.
- TextIOWrapper는 단일 클래스를 사용하여 임의 접근(읽기+쓰기)을 지원하므로 인터리브된 읽기 및 쓰기를 최적화할 수 있습니다(단, 이러한 최적화는 구현되어 있지 않습니다).
TextIOWrapper 문제
- Issue #12215 (2011-05-30): TextIOWrapper: 인터리브된 읽기 및 쓰기 문제
StreamReader 및 StreamWriter의 가능한 개선 사항
StreamReader 및 StreamWriter 클래스에 코덱 상태 읽기 및 쓰기 함수를 추가하면, 각 상태 저장 StreamReader 및 StreamWriter 클래스에서 수정하는 대신 베이스 클래스에서 상태 저장 코덱 문제를 수정할 수 있게 됩니다.
StreamReader 및 StreamWriter가 IncrementalDecoder 및 IncrementalEncoder를 사용하도록 변경할 수 있습니다.
코덱은 특정 인코딩에 맞게 최적화된 변형을 구현하거나 특정 스트림 메서드를 가로채 기능을 추가하거나 인코딩 및 디코딩 성능을 개선할 수 있습니다. TextIOWrapper는 이러한 최적화를 구현할 수 없지만, 증분 인코더와 디코더를 사용하고 읽기 및 쓰기 버퍼를 사용하므로 불완전한 입력의 오버헤드가 거의 없거나 0입니다.
다른 가변 길이 인코딩 코덱에도 훨씬 더 많은 작업을 수행할 수 있습니다. 예를 들어 UTF-8은 누락된 바이트 때문에 읽기가 끝날 무렵에 문제가 자주 발생합니다. UTF-32-BE/LE 코덱은 문자 위치에 간단히 4를 곱하여 바이트 위치를 얻을 수 있습니다.
StreamReader 및 StreamWriter의 사용
이러한 클래스는 직접 사용되는 경우가 드물고, codecs.open()을 통해 간접적으로 사용됩니다. Python 3 표준 라이브러리에서는 이러한 클래스를 사용하지 않습니다(codecs 모듈에서는 예외입니다).
일부 프로젝트는 StreamReader 및 StreamWriter를 사용하여 자체 코덱을 구현하지만, 이러한 클래스는 사용하지 않습니다.
하위 호환성
공개 API인 codecs.open 유지
codecs.open()은 내장 open() 함수로 대체할 수 있습니다. open()은 비슷한 API를 가지고 있지만 더 많은 옵션도 있습니다. 두 함수 모두 파일과 유사한 객체(같은 API)를 반환합니다.
codecs.open()은 Python 2.6까지 유니코드 모드로 텍스트 파일을 여는 유일한 방법이었습니다. 많은 Python 2 프로그램이 이 함수를 사용합니다. codecs.open()을 제거하면 Python 2에서 Python 3로 프로그램을 이식하는 데 더 많은 작업이 필요해지며, 특히 (2to3 프로그램을 사용하지 않고) 두 Python 버전에 동일한 코드베이스를 사용하는 프로젝트에서는 더욱 그렇습니다.
codecs.open()은 Python 2와의 하위 호환성을 위해 유지됩니다.
StreamReader와 StreamWriter를 폐기 예정으로 지정
StreamReader 또는 StreamWriter를 인스턴스화하면 Python 3.3에서 DeprecationWarning을 발생시켜야 합니다. 서브클래스를 정의하는 것은 DeprecationWarning을 발생시키지 않습니다.
codecs.open()은 텍스트 파일을 읽고 쓰기 위해 내장 open() 함수(TextIOWrapper)를 재사용하도록 변경될 것입니다.
대안적 접근 방식
codecs.Stream* 클래스의 폐지에 대한 대안은 codecs.open()의 이름을 codecs.open_stream()으로 변경하고, open()과 io.TextIOWrapper를 재사용하는 새로운 codecs.open() 함수를 만드는 것입니다.
부록 A: 상태 유지 코덱과 관련된 문제들
스트림과 함께 상태 유지 코덱을 올바르게 사용하는 것은 어렵습니다. 일부 경우는 codecs 모듈에서 지원되지만, io는 상태 저장 코덱과 관련된 알려진 버그가 더 이상 없습니다. codecs 모듈과 io 모듈의 주요 차이점은, codecs 모듈의 경우 각 코덱의 StreamReader 및/또는 StreamWriter 클래스에서 버그를 수정해야 하는 반면, io.TextIOWrapper에서는 한 번만 버그를 수정하면 된다는 것입니다. 다음은 상태 유지 코덱과 관련된 문제의 몇 가지 예시입니다.
상태 유지 코덱
Python은 다음과 같은 상태 유지 코덱을 지원합니다:
- 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 and 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'
io 모듈과 codecs 모듈은 이 사용 사례를 올바르게 지원합니다.
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'
io 모듈은 이 사용 사례를 지원하지만, codecs 모듈은 두 번째 쓰기에서 새 BOM을 기록하기 때문에 실패합니다(issue #12512).
추가 모드
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'
io 모듈은 이 사용 사례를 지원하지만, codecs 모듈은 두 번째 쓰기에서 새 BOM을 기록하기 때문에 실패합니다(issue #12512).
링크
Copyright
This document has been placed in the public domain.