HOWTO · Python

Python에서 파일에 바이트 쓰기

이 자습서는 Python에서 바이너리 파일에 바이트를 쓰는 방법을 보여줍니다.

이 페이지의 내용

Python에서 파일에 바이트를 쓰려면 open()를 wb 모드로 호출하고, 바이트형 객체를 파일의 write() 메서드에 전달합니다. with 블록을 사용하면 파일이 자동으로 닫힙니다. 기존 내용 뒤에 덧붙이려면 ab를 사용하세요.

각 코드 예제는 임시 디렉터리에 파일을 만듭니다. 예제의 with 블록이 끝나면 Python이 해당 디렉터리를 삭제합니다. 파일을 보존하려면 임시 경로를 원하는 저장 경로로 바꾸세요.

Python에서 파일에 바이트 쓰기

wb의 w는 파일을 쓰기 위해 엽니다. 파일이 없으면 새로 만들고, 기존 파일은 여는 즉시 내용을 모두 지웁니다. b는 바이너리 모드를 선택하므로 Python은 텍스트 인코딩이나 줄바꿈 변환 없이 바이트를 씁니다.

다음 예제를 실행해 데이터를 쓴 뒤 다시 읽어 보세요:

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as directory:
    path = Path(directory) / "example.bin"
    data = b"This is some binary data."

    with open(path, "wb") as file:
        written = file.write(data)

    with open(path, "rb") as file:
        saved = file.read()

    print(written)
    print(saved == data)
25
True

일반 버퍼링 파일에서 write()는 쓴 바이트 수를 반환합니다. 프로그램은 그 수를 출력하고 저장된 데이터가 원본과 같은지 비교합니다. 쓰기 블록이 끝나 파일이 닫히고 Python 버퍼가 플러시된 다음 데이터를 읽습니다. .bin 확장자는 파일 이름에 붙이는 관례일 뿐이며, 바이너리 모드는 모드 인수로 결정됩니다.

파일이 아직 없을 때만 새로 만들려면 xb를 사용하세요. wb와 달리 기존 파일을 덮어쓰지 않습니다. 어느 모드도 없는 상위 디렉터리를 만들지는 않습니다.

파일에 여러 바이트 시퀀스 쓰기

연속된 청크를 쓰는 동안에는 파일을 연 상태로 두세요. 각 write()는 현재 위치에서 이어서 씁니다. 반복문 안에서 파일을 wb 모드로 열면 앞서 쓴 청크가 지워집니다.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

from pathlib import Path
from tempfile import TemporaryDirectory

chunks = [b"First line.\n", b"Second line.\n", b"Third line.\n"]

with TemporaryDirectory() as directory:
    path = Path(directory) / "chunks.bin"
    with open(path, "wb") as file:
        for chunk in chunks:
            file.write(chunk)

각 청크는 bytes, bytearray 또는 적절한 버퍼 뷰와 같은 바이트형 데이터여야 합니다. 바이트 시퀀스의 이터러블을 file.writelines(chunks)에 전달할 수도 있습니다. 이름과 달리 writelines()는 구분자나 줄바꿈을 추가하지 않으므로 필요하면 청크에 직접 넣으세요. 반복문을 사용하면 전체 데이터를 메모리에서 하나로 합치지 않고 도착하는 청크를 처리할 수도 있습니다.

파일에 bytearray 쓰기

bytes 객체는 변경할 수 없지만, bytearray는 저장하기 전에 개별 바이트를 바꾸거나 시퀀스를 확장할 수 있습니다. 두 형식 모두 바이너리 파일에 바로 쓸 수 있습니다.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

from pathlib import Path
from tempfile import TemporaryDirectory

payload = bytearray(b"Line 1\n")
payload.extend(b"Line 2\n")
payload[0] = ord("l")

with TemporaryDirectory() as directory:
    path = Path(directory) / "bytearray.bin"
    with open(path, "wb") as file:
        file.write(payload)

여기서 extend()는 다른 시퀀스를 덧붙이고, 인덱스를 이용한 할당은 첫 번째 바이트를 바꿉니다. 쓰기 전에 bytes(payload)로 변환할 필요는 없습니다. 다른 곳에서 불변 값이 필요할 때 변환하세요. 나중에 bytearray를 변경해도 파일 내용은 자동으로 바뀌지 않습니다.

BytesIO 데이터를 파일에 쓰기

io 모듈의 BytesIO는 메모리에서 사용하는 바이너리 스트림입니다. getbuffer() 메서드는 내용을 별도의 bytes 객체로 복사하지 않고 내부 버퍼 전체를 가리키는 뷰를 반환합니다.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

from io import BytesIO
from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as directory:
    path = Path(directory) / "buffer.bin"
    with BytesIO() as buffer:
        buffer.write(b"Header\n")
        buffer.write(b"Payload\n")
        with buffer.getbuffer() as view:
            with open(path, "wb") as file:
                file.write(view)

이 뷰에는 스트림의 현재 위치와 관계없이 버퍼에 저장된 모든 데이터가 포함됩니다. 뷰가 살아 있는 동안에는 BytesIO 객체의 크기를 변경하거나 객체를 닫을 수 없습니다. 중첩된 컨텍스트 관리자는 스트림을 닫기 전에 뷰를 해제합니다. 대신 bytes 값이 필요하면 buffer.getvalue()를 사용하세요. buffer.read()로 복사할 때 처음부터 읽으려면 먼저 buffer.seek(0)을 호출하세요.

바이트 덧붙이기 또는 특정 위치에 쓰기

기존 내용을 자르지 않고 파일 끝에 바이트를 덧붙이려면 ab 모드로 여세요. 대상 파일이 없으면 이 모드로 새 파일을 만들 수도 있습니다. 기존 파일의 특정 오프셋에 있는 바이트를 덮어쓰려면 r+b와 파일 객체의 seek() 메서드를 사용하세요.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as directory:
    path = Path(directory) / "records.bin"
    with open(path, "wb") as file:
        file.write(b"ABCDEF")

    with open(path, "ab") as file:
        file.write(b"GH")

    with open(path, "r+b") as file:
        file.seek(2)
        file.write(b"xy")

seek(2)는 파일 시작점 기준으로 바이트 오프셋 2로 이동합니다. 다음 쓰기는 데이터를 삽입하거나 뒤에 남은 내용을 지우지 않고 두 바이트를 덮어씁니다. 더 짧은 데이터로 바꾼 뒤 끝부분을 제거해야 한다면 truncate()를 명시적으로 사용하세요.

r+b는 기존 파일이 있어야 하며 파일을 열 때 내용을 보존합니다. w+b는 읽기와 쓰기를 허용하지만 먼저 파일을 자릅니다. 위치를 지정해 갱신할 때는 추가 모드를 피하세요. 쓰기는 파일 끝을 향하므로 seek()로 이 동작을 바꾸는 것은 이식성이 없습니다. 파일 끝을 넘어 위치를 옮기는 것만으로 파일이 늘어나지는 않습니다. 이후에 쓰기를 수행하면 파일이 확장될 수 있으며, 그 사이 공간을 처리하는 방식은 파일 시스템에 따라 다릅니다.

버퍼링과 대용량 쓰기

open()로 연 일반 바이너리 파일은 기본적으로 버퍼링됩니다. buffering=8192와 같은 설정은 버퍼 크기를 요청합니다. 쓰기가 더 빨라진다거나 운영 체제에 전달되는 각 쓰기 작업의 크기가 정확히 8192바이트가 된다는 보장은 없습니다.

대량의 데이터를 생성할 때는 하나의 큰 객체를 만드는 대신 재사용 가능한 청크를 쓰세요:

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

from pathlib import Path
from tempfile import TemporaryDirectory

chunk = b"record\n" * 1024

with TemporaryDirectory() as directory:
    path = Path(directory) / "large.bin"
    with open(path, "wb", buffering=8192) as file:
        for _ in range(100):
            file.write(chunk)

이 예제는 선택적 버퍼 설정을 보여 주며 하나의 청크를 재사용합니다. 실제 작업 부하를 측정해 버퍼 크기를 정하세요. 모든 상황에 가장 적합한 크기는 없습니다. 파일을 닫으면 Python 버퍼가 플러시됩니다. 먼저 flush()를 호출하면 버퍼의 데이터가 하위 스트림으로 전달되지만, 이것만으로 물리 저장 장치에 데이터가 저장된다고 보장되지는 않습니다.

바이트 쓰기에 관한 자주 묻는 질문

쓴 바이트를 어떻게 다시 읽나요? 파일을 rb 모드로 열고 file.read()를 호출하세요. 반환값은 bytes입니다. 큰 파일은 한 번에 모두 읽지 말고 고정된 크기의 청크로 읽으세요.

바이너리 모드에서 텍스트를 쓸 수 있나요? 먼저 text.encode("utf-8") 또는 bytes(text, "utf-8")처럼 인코딩하세요. 텍스트를 복원할 때는 읽는 쪽에서 같은 인코딩을 사용해야 합니다. 텍스트만 저장하는 작업이라면 encoding="utf-8"을 지정해 텍스트 모드로 여는 편이 더 명확할 수 있습니다.

write()에서 TypeError가 발생하는 이유는 무엇인가요? 바이너리 스트림에는 바이트형 데이터가 필요합니다. str, 정수 또는 리스트를 그대로 전달하면 이 요건을 충족하지 않습니다. 텍스트는 인코딩하고, 정수 바이트 값의 시퀀스는 bytes(values)로 변환하세요. 각 정수는 0부터 255 사이여야 합니다.

빈 데이터를 쓰면 어떻게 되나요? b""를 쓰면 바이트는 기록되지 않지만, 기존 파일을 wb 모드로 열었다면 이미 그 시점에 파일 내용이 잘렸습니다. 새 데이터가 비어 있어도 기존 데이터를 보존하며 덧붙이려면 ab를 사용하세요.

대상 파일이 만들어지지 않은 이유는 무엇인가요? 상위 디렉터리가 존재하는지, 프로세스에 해당 위치에 쓸 권한이 있는지 확인하세요. 상대 경로는 프로세스의 현재 작업 디렉터리를 기준으로 해석됩니다. 애플리케이션 코드에서 관련 OSError를 처리하고, 파일을 여는 데 성공한 뒤에도 쓰기나 닫기 과정에서 오류가 발생할 수 있음을 염두에 두세요.