본문 바로가기
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

문자열과 바이트의 경계

str.encode('utf-8')는 글자를 바이트로, bytes.decode('utf-8')는 바이트를 글자로 바꿔. 인코딩을 양쪽에서 합의하지 않으면 같은 바이트도 다른 글자로 해석되거나 오류가 나.

bytes는 불변인 숫자 시퀀스야

bytes의 각 원소는 0부터 255까지의 정수라서 b'abc'[0]97을 돌려줘. 한 바이트짜리 bytes가 필요하면 [0:1]처럼 슬라이스해야 해. b'' 리터럴은 ASCII 문자와 이스케이프를 직접 담아.

점점 채울 때는 bytearray

bytearray는 바꿀 수 있는 바이트 시퀀스라서 패킷이나 파일 내용을 여러 조각으로 조립할 때 유용해. 완성 뒤 bytes로 고정할 수 있어.

복사하지 않고 바라보는 memoryview

memoryview는 같은 버퍼를 다른 창으로 보여줘. 큰 바이트 배열의 일부를 슬라이스해도 내용을 복사하지 않으므로 파서나 반복 처리에서 메모리와 시간을 아낄 수 있어. 원본의 수명과 가변성은 그대로 공유해.

Code

문자열과 bytes를 오가는 encode·decode·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 인덱스가 정수인 이유·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 리터럴과 이스케이프·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

'피파, 안녕?'을 UTF-8로 인코딩하고 bytes 길이와 원문 글자 수가 왜 다른지 설명해. 이어서 bytearray에 hello, NUL 바이트 0x00, world를 차례로 붙여. 마지막으로 1KB가 넘는 bytes를 memoryview로 감싸 슬라이스한 값이 원본 슬라이스와 같은지 확인해.

Progress

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

댓글 0

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

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