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

Python 개선 제안 한국어 번역

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

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 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 I/O 모델이 3.0을 위해 갱신되었을 때, “인코딩을 알고 있는 스트림(stream-with-known-encoding)”이라는 개념이 io.TextIOWrapper의 형태로 도입되었습니다. 이 클래스는 파이썬 3에서 텍스트 기반 I/O의 성능에 매우 중요하므로, 이 모듈에는 CPython이 기본으로 사용하는 최적화된 C 버전이 있습니다. 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()에 위임하도록 갱신합니다;
  • codecs.CodecInfo의 streamreader 및 streamwriter 속성을 포함한 레거시 codecs.Stream* 인터페이스를 Python 3.3에서 폐지합니다.

근거

StreamReader와 StreamWriter의 문제점

  • StreamReader는 줄바꿈을 변환할 수 없습니다.
  • StreamWriter는 “줄 버퍼링”(입력 텍스트에 줄바꿈이 포함되어 있으면 플러시하는 것)을 지원하지 않습니다.
  • CJK 인코딩(예: GB18030)의 StreamReader 클래스는 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): 코덱 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)보다 빠릅니다. 이 모듈은 C로 구현되어 있는 반면, codecs는 Python으로 구현되어 있습니다.
  • TextIOWrapper에는 작은 단위의 읽기, 즉 문자 단위 또는 줄 단위 읽기의 속도를 높이는 미리 읽기(readahead) 알고리즘이 있습니다(이러한 연산에서 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는 이러한 최적화를 구현할 수 없지만, 증분 인코더와 디코더를 사용하고 읽기 및 쓰기 버퍼를 사용하므로 불완전한 입력으로 인한 오버헤드는 낮거나 없습니다.

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)를 반환합니다.

Python 2.6 이전까지는 codecs.open()이 텍스트 파일을 유니코드 모드로 여는 유일한 방법이었습니다. 많은 Python 2 프로그램이 이 함수를 사용합니다. codecs.open()을 제거하면 프로그램을 Python 2에서 Python 3로 이식하는 작업이 늘어나며, (2to3 프로그램을 사용하지 않고) 두 Python 버전에서 같은 코드 베이스를 사용하는 프로젝트는 특히 그렇습니다.

codecs.open()은 Python 2와의 하위 호환성을 위해 유지됩니다.

StreamReader와 StreamWriter 폐지 예고

Python 3.3에서 StreamReader나 StreamWriter의 인스턴스를 만들면 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에서는 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

읽기와 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).

링크

Footnotes