C.W.K.
Stream
Lesson 07 of 07 · published

Bytes / bytearray / memoryview — 바이너리 다루기

~22 min · bytes, bytearray, memoryview, encoding, binary

Level 0호기심
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

대부분 튜토리얼이 빼먹는 트랙 — 그리고 못 빼먹어

언젠가는 raw bytes 를 만지게 돼 — 바이너리 파일 읽기, 네트워크 메시지 디코딩, 비밀번호 해싱, HTTP body 보내기. Python 의 바이너리 패밀리는 셋 — bytes (immutable), bytearray (mutable), memoryview (제로카피 윈도). 대부분 튜토리얼이 살짝 건드리고 넘어가. cwkPippa 의 어댑터들은 셋 다 만져.

str vs bytes — Python 3 의 단호한 선

Python 3 에서 str 은 유니코드 *문자* 의 시퀀스. bytes 는 *raw 8-bit 숫자* 의 시퀀스. 둘은 호환 안 돼. str → bytes 는 *encode* (디폴트 UTF-8), bytes → str 은 *decode*. b- 접두사 리터럴 b"hello" 가 bytes — encode 안 거쳐도 됨. 둘을 concat 하면 TypeError. 이 단호함이 Python 2 를 평생 괴롭힌 버그 한 부류를 막아줘.

bytes 인덱싱하면 정수가 나와

가장 놀라운 부분 중 하나 — b"abc"[0]97 이야. b"a" 가 아니라. bytes 순회도 정수 yield. 한 바이트짜리 객체가 필요하면 슬라이스 — b"abc"[0:1]. *bytes 는 숫자* 라는 mental model 에는 일관적인데, 문자열처럼 행동할 거라 기대하면 물려.

bytearray — bytes 가 변해야 할 때

bytes 는 immutable — 얼린 문자열. bytearray 가 mutable 사촌. ba[i] = 0xFF, ba.append(byte), del ba[i] 다 됨. use case — 점점 채워가는 버퍼 (네트워크 메시지 받기, in-memory 로 바이너리 파일 만들고 flush).

memoryview — 복사 없이 슬라이스

bytes 슬라이스하면 사본이 생겨. 10MB 버퍼에서 파서 loop 가 수천 번 슬라이스 한다 — 그 복사가 성능을 죽여. memoryview 는 같은 버퍼를 *제로카피* 인터페이스로 감싸 — 슬라이스해도 같은 메모리의 또 다른 view. struct, numpy, 바이너리 프로토콜 파서 라이브러리들이 쓰는 기술. day-1 에 직접 쓸 일은 없어도 진지한 라이브러리에서 보게 될 거야.

원칙: 헷갈리면 *경계에서 encode/decode*. 프로그램 내부에선 str. 소켓/파일/해시 함수에 쓰는 순간 bytes 로 encode. 그 반대 방향에선 decode 해서 들어와. 이 패턴이 나머지 코드를 깔끔하게 유지해줘.

Code

encode / decode — str 과 bytes 의 다리·python
# str -> bytes (encode)
text = "안녕, 아빠"
b = text.encode("utf-8")
print(b)                  # b'\xec\x95\x88\xeb\x85\x95, \xec\x95\x84\xeb\xb9\xa0'
print(len(b))             # 14   — UTF-8 은 한글 한 글자에 3 바이트
print(len(text))          # 6    — Python 은 글자로 셈

# bytes -> str (decode)
back = b.decode("utf-8")
print(back == text)       # True

# 잘못된 encoding 은 raise
try:
    b.decode("ascii")
except UnicodeDecodeError as e:
    print("실패:", e)
bytes 인덱싱은 int — 놀라움·python
data = b"hello"

print(data[0])            # 104   — int!
print(data[0:1])          # b'h'  — 슬라이스는 bytes

# 순회도 int yield
for byte in b"abc":
    print(byte)
# 97
# 98
# 99

# 글자 원하면 decode 먼저
for ch in b"abc".decode():
    print(ch)
# a
# b
# c
bytes 리터럴 — 이스케이프와 b 접두사·python
# b'' 리터럴 — encoding 단계 없이 직접
raw = b"\x00\x01\x02\xff"
print(raw)                # b'\x00\x01\x02\xff'
print(list(raw))          # [0, 1, 2, 255]

# str 과 bytes concat 은 TypeError
try:
    "hello " + b"world"
except TypeError as e:
    print(e)              # can only concatenate str (not 'bytes') to str

# bytes.fromhex / .hex() — hex 와 round-trip
h = b"\xde\xad\xbe\xef".hex()
print(h)                  # 'deadbeef'
print(bytes.fromhex("deadbeef"))  # b'\xde\xad\xbe\xef'
bytearray — 점점 채우는 바이너리 빌더·python
buf = bytearray()           # 빈 버퍼
buf.extend(b"HEAD")
buf.append(0x00)
buf.extend(b"BODY")
print(buf)                  # bytearray(b'HEAD\x00BODY')

# in-place 변경
buf[0] = 0xff
print(buf)                  # bytearray(b'\xffEAD\x00BODY')

# use case — 고정 크기 버퍼 채우기
buf = bytearray(8)          # 0 바이트 8 개
buf[0:4] = b"\xde\xad\xbe\xef"
print(buf)                  # bytearray(b'\xde\xad\xbe\xef\x00\x00\x00\x00')
memoryview — 복사 없이 슬라이스·python
data = bytearray(b"hello world" * 1000)   # ~11KB 버퍼

# bytes/bytearray 슬라이싱은 *복사*
slice_copy = data[100:200]

# memoryview 는 제로카피 view
mv = memoryview(data)
slice_view = mv[100:200]

# 읽기는 동일
print(bytes(slice_view[:5]))      # b'orld '

# view 통해 변경하면 원본 버퍼에 써짐
slice_view[0] = 0x58              # 'X'
print(data[100:105])              # bytearray(b'Xrld ')

# 왜 중요? 10MB 데이터 파싱하면서 수천 번 슬라이스 — 복사 안 일어나서 싸.
mv.release()

External links

Exercise

(a) "피파, 안녕?" 을 UTF-8 로 encode. bytes 객체, 그 길이, 원본 문자열 길이 출력. 왜 다를까? (b) bytearray 처음부터 — "hello" 의 바이트 추가, NUL 바이트 (0x00), "world". 결과 출력. (c) 1KB 넘는 bytes 객체 만들어서 memoryview 로 감싸. memoryview 슬라이스가 원본 슬라이스와 같은 바이트 읽는지 확인. (보너스 — 백만 번 슬라이스 시간 측정 비교.)

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.