Python의 `io` 모듈
이 문서에서는 Python의 io 모듈에 대해 설명합니다.
실제 예제를 통해 Python의 io 모듈을 설명하겠습니다.
YouTube Video
Python의 io 모듈
입출력 처리는 파일, 네트워크, 표준 입출력 등 모든 종류의 데이터 작업의 기초를 이룹니다. Python의 io 모듈은 이러한 입출력 처리를 통합하는 추상 클래스 집합을 제공합니다. 이 모듈을 이해하는 핵심 개념은 ‘스트림’이라는 아이디어입니다.
스트림이란 무엇인가요?
스트림은 데이터를 순차적이고 연속적으로 읽고 쓰는 추상적인 흐름입니다.
파일을 바이트 단위로 읽거나 네트워크를 통해 데이터를 송수신할 때, 이러한 모든 작업들은 데이터 스트림으로 처리할 수 있습니다.
이러한 메커니즘을 추상화함으로써 파일, 메모리, 네트워크 등 다양한 I/O 소스를 읽기와 쓰기 같은 공통된 연산으로 다룰 수 있습니다.
Python의 io 모듈은 스트림을 위한 통합 인터페이스를 제공하여 텍스트와 이진 데이터 모두를 효율적으로 처리할 수 있습니다.
io 모듈의 기본 구조
io 모듈은 스트림의 성질에 따라 3단계 계층 구조를 가지고 있습니다.
-
Raw 계층 (
RawIOBase)RawIOBase는 OS 파일 디스크립터나 장치 등 가장 하위 수준의 바이트 단위 입출력을 처리합니다. -
버퍼 계층 (
BufferedIOBase)BufferedIOBase는 입출력 효율을 높이기 위해 캐시(버퍼)를 제공합니다.BufferedReader와BufferedWriter가 대표적인 예입니다. -
텍스트 계층 (
TextIOBase)TextIOBase는 바이트 시퀀스를 문자열로 변환하고 인코딩을 처리합니다. 일반적으로open()함수로 파일을 열 때는 이 계층의TextIOWrapper가 사용됩니다.
이 구조 덕분에 io 모듈은 텍스트 I/O와 이진 I/O를 명확히 구분하며, 유연한 조합이 가능합니다.
io 모듈의 기본 구조
가장 낮은 계층에서 RawIOBase가 OS 파일 디스크립터를 다루고, 그 위에 BufferedIOBase가 캐시를 추가하며, 최상위 계층인 TextIOBase가 문자열 변환을 처리합니다.
1import io
2
3# Check the core base classes hierarchy
4print(io.IOBase.__subclasses__())- 이 코드는
IOBase로부터 상속받는 추상 클래스의 그룹을 확인하는 예입니다.TextIOBase,BufferedIOBase,RawIOBase를 확인할 수 있으며, 계층적 구조를 알 수 있습니다.
io.IOBase: 모든 I/O의 기본 클래스
IOBase는 모든 I/O 객체의 추상 기본 클래스로, close(), flush(), seekable() 등 공통 메서드를 정의합니다. 직접 사용되는 일은 드물고, 보통은 하위 클래스에서 접근합니다.
1import io
2
3f = io.StringIO("data")
4print(f.seekable()) # True
5print(f.readable()) # True
6print(f.writable()) # True
7f.close()- 이 예시는
IOBase의 공통 메서드가 상위 클래스에서도 사용 가능함을 보여줍니다.seekable()과readable()은 스트림의 속성을 확인하는 데 유용합니다.
io.RawIOBase: 가장 하위 계층
RawIOBase는 OS 파일 디스크립터에 가장 가까운 계층으로, 버퍼링을 수행하지 않습니다. 일반적인 구현은 FileIO로, 바이트 단위로 읽고 씁니다.
1import io, os
2
3# Create a low-level FileIO object (no buffering)
4fd = os.open('raw_demo.bin', os.O_RDWR | os.O_CREAT)
5raw = io.FileIO(fd, mode='w+')
6raw.write(b'abc123')
7raw.seek(0)
8print(raw.read(6)) # b'abc123'
9raw.close()FileIO는RawIOBase의 구체적인 구현으로, 모든 읽기와 쓰기가bytes로 수행됩니다. 상위 계층인BufferedIOBase와 결합하면 효율성을 높일 수 있습니다.
io.BufferedIOBase: 중간 계층(버퍼 포함)
BufferedIOBase는 버퍼링을 수행하는 중간 계층으로, 디스크 접근을 더 효율적으로 만듭니다. 주요 구현 클래스는 BufferedReader, BufferedWriter, BufferedRandom, BufferedRWPair입니다.
1import io
2
3# Create a buffered binary stream on top of a BytesIO (simulate file)
4base = io.BytesIO()
5buffered = io.BufferedWriter(base)
6buffered.write(b'Python IO buffering')
7buffered.flush()
8base.seek(0)
9print(base.read()) # b'Python IO buffering'- 이 예제에서는
BufferedWriter로 쓴 데이터가 일시적으로 메모리 버퍼에 저장되며,flush()를 호출하면 실제로 하위 계층으로 전송됩니다.
BufferedReader 예제
BufferedReader는 읽기 전용 버퍼 스트림으로, peek() 및 read()를 사용한 효율적인 읽기를 지원합니다.
1import io
2
3stream = io.BytesIO(b"1234567890")
4reader = io.BufferedReader(stream)
5print(reader.peek(5)) # b'12345' (non-destructive)
6print(reader.read(4)) # b'1234'
7print(reader.read(3)) # b'567'peek()는 데이터를 '들여다보는' 기능만 하며, 포인터는 이동하지 않습니다.read()와 조합하여 버퍼링을 유연하게 제어할 수 있습니다.
io.TextIOBase: 텍스트 전용 계층
TextIOBase는 문자열 처리를 위한 추상화 계층으로, 내부적으로 디코딩과 인코딩을 수행합니다. 대표적인 구현 클래스는 TextIOWrapper입니다.
1import io
2
3# Wrap a binary stream to handle text encoding
4binary = io.BytesIO()
5text_stream = io.TextIOWrapper(binary, encoding='utf-8')
6text_stream.write("\u3053\u3093\u306B\u3061\u306F")
7text_stream.flush()
8
9# Reset stream position
10binary.seek(0)
11
12# Read bytes once
13data = binary.read()
14
15# Show both raw bytes and decoded text
16print("Raw bytes:", data)
17print("Decoded text:", data.decode('utf-8'))- 이 예제에서는
TextIOWrapper가 문자열을 UTF-8로 인코딩하여 하위 이진 스트림에 기록합니다.
TextIOWrapper로 읽기 예제
읽을 때는 디코딩이 자동으로 수행됩니다.
1import io
2
3binary_data = io.BytesIO("Python I/O".encode('utf-8'))
4text_reader = io.TextIOWrapper(binary_data, encoding='utf-8')
5print(text_reader.read()) # 'Python I/O'TextIOWrapper는 텍스트 입출력을 위한 기본 클래스이며, 거의 모든 고수준 파일 연산의 토대가 됩니다.
io.StringIO: 메모리 내 텍스트 스트림
StringIO는 메모리 내에서 문자열을 마치 파일처럼 다룰 수 있게 해주는 클래스입니다. 입출력 테스트나 임시 데이터 생성에 유용합니다.
1import io
2
3text_buf = io.StringIO()
4text_buf.write("In-memory text stream")
5text_buf.seek(0)
6print(text_buf.read()) # 'In-memory text stream'StringIO는 디스크를 사용하지 않고 파일처럼 조작이 가능하여 단위 테스트 등에 널리 사용됩니다.
io.BytesIO: 메모리 내 이진 스트림
BytesIO는 바이트 시퀀스(bytes) 처리를 위한 메모리 내 파일 클래스입니다. 이진 처리나 데이터 압축 등 파일을 사용하지 않는 상황에서 유용합니다.
1import io
2
3buf = io.BytesIO()
4buf.write(b'\x01\x02\x03')
5buf.seek(0)
6print(list(buf.read())) # [1, 2, 3]BytesIO는BufferedIOBase와 동일한 인터페이스를 가지며, 다양한 파일 API의 대체로 활용할 수 있습니다.
사용자 정의 스트림(오리지널 클래스 생성)
io의 클래스는 확장 가능하여, 자신만의 스트림 클래스를 만들 수 있습니다. 아래는 쓸 때 모든 텍스트를 대문자로 변환하는 TextIOBase 하위 클래스의 예제입니다.
1import io
2
3class UpperTextIO(io.TextIOBase):
4 def __init__(self):
5 self.buffer = ""
6 def write(self, s):
7 self.buffer += s.upper()
8 return len(s)
9
10u = UpperTextIO()
11u.write("hello io")
12print(u.buffer) # "HELLO IO"TextIOBase의 규약만 지킨다면, 이와 같은 사용자 정의 동작을 얼마든지 구현할 수 있습니다. 파일이나 네트워크 등 특정 목적으로 스트림을 확장하는 것도 쉽습니다.
요약
io 모듈은 입출력 처리를 추상 클래스와 구체 클래스의 계층으로 정리합니다.
RawIOBase는 OS 수준의 바이트 I/O를 위한 클래스입니다.BufferedIOBase는 효율적인 캐시 계층을 제공하는 클래스입니다.TextIOBase는 문자열의 읽기와 쓰기를 관리하는 클래스입니다.StringIO와BytesIO는 메모리 내 스트림을 제공하는 클래스입니다.
이러한 클래스들을 이해함으로써 Python의 I/O 시스템 동작을 정확히 파악하고, 파일 처리, 네트워크 통신, 테스트 스트림 설계에 응용할 수 있습니다.
위의 기사를 보면서 Visual Studio Code를 사용해 우리 유튜브 채널에서 함께 따라할 수 있습니다. 유튜브 채널도 확인해 주세요.