PEP 3116 – 새로운 I/O
- Author:
- Daniel Stutzbach <daniel at stutzbachenterprises.com>, Guido van Rossum <guido at python.org>, Mike Verdone <mike.verdone at gmail.com>
- Status:
- Final
- Type:
- Standards Track
- Created:
- 26-Feb-2007
- Python-Version:
- 3.0
- Post-History:
- 26-Feb-2007
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
근거와 목표
Python에서는 read() 및 write() 호출을 통해 사용할 수 있는 다양한 스트림과 유사한(즉, 파일과 유사한) 객체를 사용할 수 있습니다. read() 및 write()를 제공하는 것은 무엇이든 스트림과 유사합니다. 그러나 readline() 또는 seek()와 같이 더 특수하면서도 매우 유용한 함수는 모든 스트림과 유사한 객체에서 사용 가능할 수도 있고 그렇지 않을 수도 있습니다. Python에는 버퍼링 및 텍스트 처리 기능을 추가할 수 있는 기본 바이트 기반 I/O 스트림 사양이 필요합니다.
정의된 원시 바이트 기반 I/O 인터페이스가 있으면 모든 바이트 기반 I/O 클래스 위에 버퍼링 및 텍스트 처리 계층을 추가할 수 있습니다. 동일한 버퍼링 및 텍스트 처리 로직을 파일, 소켓, 바이트 배열 또는 Python 프로그래머가 개발한 사용자 지정 I/O 클래스에 사용할 수 있습니다. 스트림의 표준 정의를 개발하면 read() 및 write()와 같은 스트림 기반 작업을 fileno() 및 isatty()와 같은 구현별 작업과 분리할 수 있습니다. 이는 프로그래머가 스트림을 스트림으로 사용하는 코드를 작성하도록 장려하며, 모든 스트림이 파일별 또는 소켓별 작업을 지원하도록 요구하지 않습니다.
새로운 I/O 사양은 Java I/O 라이브러리와 유사하도록 의도되었지만, 전반적으로 덜 혼란스럽습니다. 새로운 I/O 세계를 다루고 싶지 않은 프로그래머는 open() 팩토리 메서드가 이전 스타일 파일 객체와 하위 호환성을 갖는 객체를 생성할 것으로 예상할 수 있습니다.
사양
Python I/O 라이브러리는 원시 I/O 계층, 버퍼링된 I/O 계층 및 텍스트 I/O 계층의 세 계층으로 구성됩니다. 각 계층은 추상 베이스 클래스로 정의되며, 여러 구현을 가질 수 있습니다. 원시 I/O 계층과 버퍼링된 I/O 계층은 바이트 단위를 다루는 반면, 텍스트 I/O 계층은 문자 단위를 다룹니다.
원시 I/O
원시 I/O의 추상 베이스 클래스는 RawIOBase입니다. 여기에는 적절한 운영 체제 호출을 감싸는 여러 메서드가 있습니다. 이러한 함수 중 하나가 객체에서 의미가 없는 경우 구현은 IOError 예외를 발생시켜야 합니다. 예를 들어 파일이 읽기 전용으로 열려 있으면 .write() 메서드는 IOError 예외를 발생시킵니다. 또 다른 예로 객체가 소켓을 나타내는 경우 .seek(), .tell() 및 .truncate()는 IOError 예외를 발생시킵니다. 일반적으로 이러한 함수 중 하나를 호출하면 정확히 하나의 운영 체제 호출로 매핑됩니다.
.read(n: int) -> bytes객체에서 최대n바이트를 읽어 반환합니다. 운영 체제 호출이n바이트보다 적게 반환하면n바이트보다 적은 수가 반환될 수 있습니다. 0바이트가 반환되면 파일 끝임을 나타냅니다. 객체가 비블로킹 모드이고 사용할 수 있는 바이트가 없으면 호출은None을 반환합니다.
.readinto(b: bytes) -> int객체에서 최대len(b)바이트를 읽어b에 저장하고, 읽은 바이트 수를 반환합니다.len(b)보다 적은 바이트가 읽힐 수 있으며, 0은 파일 끝을 나타냅니다. 비차단 객체에 사용할 수 있는 바이트가 없으면None이 반환됩니다.b의 길이는 절대 변경되지 않습니다.
.write(b: bytes) -> int작성된 바이트 수를 반환하며,< len(b)보다 작을 수 있습니다.
.seek(pos: int, whence: int = 0) -> int
.tell() -> int
.truncate(n: int = None) -> int
.close() -> None
또한 몇 가지 다른 메서드를 정의합니다.
.readable() -> bool객체가 읽기용으로 열렸으면True를 반환하고, 그렇지 않으면False를 반환합니다.False인 경우 호출하면.read()가IOError를 발생시킵니다.
.writable() -> bool객체가 쓰기용으로 열렸으면True를 반환하고, 그렇지 않으면False를 반환합니다.False인 경우 호출하면.write()와.truncate()가IOError를 발생시킵니다.
.seekable() -> bool객체가 임의 접근(예: 디스크 파일)을 지원하면True를 반환하고, 객체가 순차 접근(예: 소켓, 파이프 및 tty)만 지원하면False를 반환합니다.False인 경우.seek(),.tell(),.truncate()를 호출하면 IOError가 발생합니다.
.__enter__() -> ContextManager컨텍스트 관리 프로토콜입니다.self를 반환합니다.
.__exit__(...) -> None컨텍스트 관리 프로토콜입니다..close()와 같습니다.
그리고 RawIOBase구현이 기반 파일 디스크립터에서 작동하는 경우에만 .fileno() 멤버 함수를 추가로 제공해야 합니다. 이는 구현에서 구체적으로 정의하거나, 믹스인 클래스를 사용할 수 있습니다(이 부분은 결정해야 합니다).
.fileno() -> int기반 파일 디스크립터(정수)를 반환합니다.
처음에는 RawIOBase 인터페이스를 구현하는 세 가지 구현인 FileIO, SocketIO(socket 모듈에 있음) 및 ByteIO를 제공합니다. 사용자에게 제공된 정보만으로는 충분하지 않을 수 있으므로 각 구현은 객체가 임의 접근을 지원하는지 판별해야 합니다(open("/dev/tty", "rw") 또는 open("/tmp/named-pipe", "rw")를 고려하십시오). 예를 들어 FileIO는 seek() 시스템 호출을 실행하여 이를 판별할 수 있으며, 오류가 반환되면 객체는 임의 접근을 지원하지 않습니다. 각 구현은 해당 유형에 적합한 추가 메서드를 제공할 수 있습니다. ByteIO 객체는 Python 2의 cStringIO 라이브러리와 유사하지만, 문자열 대신 새로운 bytes 유형에서 작동합니다.
버퍼링된 입출력
다음 계층은 파일과 유사한 객체에 더 효율적으로 접근할 수 있도록 제공되는 버퍼링된 I/O 계층입니다. 모든 버퍼링된 I/O 구현의 추상 베이스 클래스는 BufferedIOBase이며, RawIOBase와 유사한 메서드를 제공합니다.
.read(n: int = -1) -> bytes객체에서 다음n바이트를 반환합니다. 파일 끝에 도달했거나 객체가 비블로킹인 경우n바이트보다 적은 바이트를 반환할 수 있습니다. 0바이트는 파일 끝을 나타냅니다. 이 메서드는 바이트를 모으기 위해RawIOBase.read()를 여러 번 호출할 수 있으며, 필요한 바이트가 모두 이미 버퍼링되어 있다면RawIOBase.read()를 전혀 호출하지 않을 수도 있습니다.
.readinto(b: bytes) -> int
.write(b: bytes) -> intb바이트를 버퍼에 씁니다. 바이트가 Raw I/O 객체에 즉시 기록된다고 보장되지는 않으며, 버퍼링될 수 있습니다.len(b)을 반환합니다.
.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
또한 추상 베이스 클래스는 다음과 같은 멤버 변수를 제공합니다.
.raw기반이 되는RawIOBase객체에 대한 참조입니다.
BufferedIOBase의 메서드 시그니처는 대부분 RawIOBase의 메서드 시그니처와 동일하지만(write()는 None을 반환하고 read()의 인자는 선택 사항이라는 점은 예외입니다), 의미는 다를 수 있습니다. 특히 BufferedIOBase구현은 요청된 것보다 많은 데이터를 읽거나 버퍼를 사용하여 데이터 쓰기를 지연할 수 있습니다. 대부분의 경우 이는 사용자에게 투명합니다(예를 들어 다른 디스크립터를 통해 같은 파일을 여는 경우는 제외합니다). 또한 원시 읽기는 특별한 이유 없이 짧은 읽기를 반환할 수 있지만, 버퍼링된 읽기는 EOF에 도달한 경우에만 짧은 읽기를 반환합니다. 원시 쓰기는 비블로킹 I/O가 활성화되지 않은 경우에도 짧은 개수를 반환할 수 있는 반면, 버퍼링된 쓰기는 모든 바이트를 기록하거나 버퍼링할 수 없으면 IOError를 발생시킵니다.
아래에 설명된 BufferedIOBase추상 베이스 클래스의 구현은 네 가지입니다.
BufferedReader
BufferedReader구현은 순차 액세스 읽기 전용 객체를 위한 것입니다. 이 객체의 .flush()메서드는 아무 작업도 하지 않습니다.
BufferedWriter
BufferedWriter 구현은 순차 접근 방식의 쓰기 전용 객체를 위한 것입니다. 해당 .flush()메서드는 캐시된 모든 데이터를 기반이 되는 RawIOBase 객체에 기록하도록 강제합니다.
BufferedRWPair
BufferedRWPair 구현은 소켓 및 tty와 같은 순차 접근 방식의 읽기-쓰기 객체를 위한 것입니다. 이러한 객체의 읽기 스트림과 쓰기 스트림은 완전히 독립적이므로, BufferedReader 및 BufferedWriter 인스턴스를 단순히 통합하여 구현할 수 있습니다. BufferedWriter의 .flush()메서드와 동일한 의미를 갖는 .flush()메서드를 제공합니다.
BufferedRandom
BufferedRandom 구현은 읽기 전용, 쓰기 전용 또는 읽기-쓰기 여부와 관계없이 모든 임의 접근 객체를 위한 것입니다. 순차 접근 객체에서 작동하는 이전 클래스와 비교하면, BufferedRandom 클래스는 사용자가 스트림의 위치를 재지정하기 위해 .seek()를 호출하는 상황을 처리해야 합니다. 따라서 BufferedRandom의 인스턴스는 객체 내의 논리적 위치와 실제 위치를 모두 추적해야 합니다. 캐시된 모든 쓰기 데이터를 기반이 되는 RawIOBase 객체에 기록하고 캐시된 모든 읽기 데이터를 잊도록 강제하는 .flush()메서드를 제공합니다(따라서 이후 읽기에서는 디스크에 다시 접근해야 합니다).
Q: 읽기-쓰기 객체에서 읽기와 쓰기 사이를 전환하면 .flush()가 수반된다고 사양에서 규정해야 합니까? 아니면 사용자가 의존해서는 안 되는 구현상의 편의일 뿐입니까?
읽기 전용 BufferedRandom 객체에서는 .writable()가 False를 반환하며, .write()및 .truncate()메서드는 IOError를 발생시킵니다.
쓰기 전용 BufferedRandom 객체에서는 .readable()가 False를 반환하며, .read()메서드는 IOError를 발생시킵니다.
텍스트 I/O
텍스트 I/O 계층은 스트림에서 문자열을 읽고 쓰는 함수를 제공합니다. 새로운 기능으로는 범용 줄바꿈과 문자 집합 인코딩 및 디코딩이 있습니다. Text I/O 계층은 TextIOBase 추상 베이스 클래스에 의해 정의됩니다. 이 계층은 BufferedIOBase 메서드와 유사한 여러 메서드를 제공하지만, 바이트 단위가 아니라 문자 단위로 작동합니다. 이러한 메서드는 다음과 같습니다.
.read(n: int = -1) -> str
.write(s: str) -> int
.tell() -> object현재 파일 위치를 설명하는 쿠키를 반환하십시오. 쿠키에 대해 지원되는 유일한 용도는 whence를 0으로 설정한 .seek()와 함께 사용하는 것입니다(즉, 절대 탐색).
.seek(pos: object, whence: int = 0) -> intpos위치로 탐색하십시오.pos가 0이 아니면,.tell()에서 반환된 쿠키여야 하며whence는 0이어야 합니다.
.truncate(pos: object = None) -> intBufferedIOBase.truncate()와 같지만,pos가None이 아닌 경우에는 이전에.tell()에서 반환된 쿠키여야 합니다.
원시 I/O와 달리, .seek()의 단위는 지정되어 있지 않습니다. 일부 구현(예: StringIO는)은 문자를 사용하고, 다른 구현(예: TextIOWrapper는)은 바이트를 사용합니다. 0에 대한 특별한 경우는 이전에 .tell()을 호출하지 않고도 스트림의 시작 또는 끝으로 이동할 수 있도록 하기 위한 것입니다. 구현은 .tell()에서 반환되는 쿠키에 스트림 인코더 상태를 포함할 수 있습니다.
TextIOBase구현은 기본 BufferedIOBase 객체에 전달하는 여러 메서드도 제공합니다.
.flush() -> None
.close() -> None
.readable() -> bool
.writable() -> bool
.seekable() -> bool
TextIOBase클래스 구현은 다음 메서드도 추가로 제공합니다.
.readline() -> str줄바꿈 또는 EOF까지 읽고 해당 줄을 반환하며, EOF에 즉시 도달하면""을 반환합니다.
.__iter__() -> Iterator파일에서 줄을 반환하는 이터레이터를 반환합니다(실제로self입니다).
.next() -> strreadline()과 같지만 EOF에 즉시 도달하면StopIteration을 발생시킵니다.
Python 라이브러리는 두 가지 구현을 제공합니다. 기본 구현인 TextIOWrapper는 버퍼링된 I/O 객체를 래핑합니다. 각 TextIOWrapper객체에는 “.buffer”라는 속성이 있으며, 이 속성은 기본 BufferedIOBase 객체에 대한 참조를 제공합니다. 해당 초기화 함수의 시그니처는 다음과 같습니다.
.__init__(self, buffer, encoding=None, errors=None, newline=None, line_buffering=False)buffer는TextIOWrapper로 래핑할BufferedIOBase객체에 대한 참조입니다.
encoding은 바이트 표현과 문자 표현 사이의 변환에 사용할 인코딩을 나타냅니다.None이면 시스템의 로캘 설정이 기본값으로 사용됩니다.
errors는 오류 처리를 나타내는 선택적 문자열입니다.encoding을 설정할 수 있는 경우에는 언제든지 설정할 수 있습니다. 기본값은'strict'입니다.
newline은None,'','\n','\r'또는'\r\n'일 수 있으며, 그 밖의 모든 값은 사용할 수 없습니다. 이는 줄 끝 처리를 제어합니다. 다음과 같이 작동합니다:
- 입력 시
newline이None이면 보편적 개행 모드가 활성화됩니다. 입력의 줄은'\n','\r'또는'\r\n'으로 끝날 수 있으며, 호출자에게 반환되기 전에 이것들은'\n'으로 변환됩니다.''이면 보편적 개행 모드가 활성화되지만, 줄 끝은 변환되지 않은 상태로 호출자에게 반환됩니다. 그 밖의 적법한 값 중 하나이면 입력 줄은 주어진 문자열로만 종료되며, 줄 끝은 변환되지 않은 상태로 호출자에게 반환됩니다. (즉,newline이None인 경우에만'\n'으로 변환됩니다.)- 출력 시
newline이None이면 기록되는 모든'\n'문자는 시스템 기본 줄 구분자인os.linesep으로 변환됩니다.newline이''이면 변환이 수행되지 않습니다.newline이 그 밖의 적법한 값 중 하나이면 기록되는 모든'\n'문자는 주어진 문자열로 변환됩니다. (출력의 변환을 안내하는 규칙은 입력의 경우와 다릅니다.)
line_buffering이 True이면 기록되는 문자열에'\n'또는'\r'문자가 하나 이상 포함된 경우write()호출이flush()를 암시하게 됩니다. 기반 스트림이 TTY 장치임을 감지하거나buffering인자에1을 전달하면open()이 이를 설정합니다.
newline매개변수에 대한 추가 참고 사항입니다:
'\r'지원은'\r'줄 끝을 사용하여 파일을 생성하는 일부 OSX 애플리케이션에 여전히 필요합니다. 텍스트로 내보낼 때의 Excel과 Adobe Illustrator EPS 파일이 가장 일반적인 예입니다.- 변환이 활성화되어 있으면 읽기 또는 쓰기에 어떤 메서드가 호출되는지와 관계없이 변환이 수행됩니다. 예를 들어,
f.read()는 항상''.join(f.readlines())와 같은 결과를 생성합니다.- 입력에서 변환 없는 유니버설 줄 바꿈이 요청된 경우(즉,
newline=''인 경우), 시스템 읽기 연산이'\r'로 끝나는 버퍼를 반환하면, 뒤에'\n'이 오는지 여부를 확인하기 위해 또 다른 시스템 읽기 연산을 수행합니다. 변환을 사용하는 보편적 개행 모드에서는 두 번째 시스템 읽기 작업이 다음 읽기 요청까지 연기될 수 있으며, 다음 시스템 읽기 작업이'\n'으로 시작하는 버퍼를 반환하면 해당 문자는 단순히 버려집니다.
또 다른 구현인 StringIO는 기반 Buffered I/O 객체 없이 파일과 유사한 TextIO 구현을 생성합니다. BytesIO 객체를 TextIOWrapper로 래핑하여 유사한 기능을 제공할 수도 있지만, StringIO 객체는 실제로 인코딩과 디코딩을 수행할 필요가 없으므로 훨씬 높은 효율을 제공합니다. String I/O 객체는 인코딩된 문자열을 있는 그대로 저장할 수 있습니다. StringIO객체의 __init__ 시그니처는 초기값을 지정하는 선택적 문자열을 받으며, 초기 위치는 항상 0입니다. 인코딩이나 개행 변환을 지원하지 않으며, 항상 작성한 문자를 정확히 그대로 다시 읽습니다.
유니코드 인코딩/디코딩 문제
나중에 인코딩 및 오류 처리 설정을 변경할 수 있도록 해야 합니다. 유니코드 문제와 모호성(예: 발음 구별 부호, 서로게이트, 인코딩의 유효하지 않은 바이트)에 직면했을 때 Text I/O 작업의 동작은 유니코드 encode()/decode() 메서드의 동작과 동일해야 합니다. UnicodeError가 발생할 수 있습니다.
구현 참고 사항: codecs 모듈에서 제공하는 인프라의 상당 부분을 재사용할 수 있어야 합니다. 필요한 정확한 API를 제공하지 않는다면 바퀴를 다시 발명하지 않도록 이를 리팩터링해야 합니다.
비블로킹 I/O
비블로킹 I/O는 Raw I/O 수준에서만 완전히 지원됩니다. Raw 객체가 비블로킹 모드에 있고 작업이 블로킹될 경우 .read()와 .readinto()는 None을 반환하는 반면, .write()는 0을 반환합니다. 객체를 비블로킹 모드로 설정하려면 사용자는 fileno를 추출하여 직접 설정해야 합니다.
Buffered I/O 및 Text I/O 계층에서 비블로킹 조건으로 인해 읽기 또는 쓰기가 실패하면 IOError를 발생시키며, errno는 EAGAIN으로 설정됩니다.
원래는 Raw I/O 동작을 상위 계층으로 전파하는 것을 고려했지만, 많은 예외적인 경우와 문제가 제기되었습니다. 이러한 문제를 해결하려면 Buffered I/O 및 Text I/O 계층을 상당히 변경해야 했을 것입니다. 예를 들어, Buffered 비블로킹 객체에서 .flush()는 무엇을 해야 합니까? 사용자는 객체에 “버퍼에서 가능한 한 많이 쓰되, 블로킹하지 마십시오”라고 어떻게 지시할 수 있습니까? 사용 가능한 모든 데이터를 반드시 플러시하지는 않는 비블로킹 .flush()는 직관에 어긋납니다. 이러한 계층에서 비블로킹 객체와 블로킹 객체의 의미가 매우 다르므로, 이를 단일 형식으로 결합하려는 노력을 포기하기로 합의했습니다.
open() 내장 함수
open() 내장 함수는 다음 의사 코드로 지정됩니다.:
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)
Copyright
This document has been placed in the public domain.