[Python] byte 타입을 다루는 방법(bytearray, struct 모듈)


Development note/Python  2020. 2. 6. 23:03

안녕하세요. 명월입니다.


이 글은 Python에서 byte 타입을 다루는 방법(bytearray, struct 모듈)에 대한 글입니다.


먼저 byte타입을 다루기 위해서 byte를 이해할 필요가 있습니다.

프로그램에서 byte란 간단하게 8bit로 이루어진 byte단위입니다. 1bit는 2진 데이터의 하나이므로 8bit는 2^8승, 즉 십진수로 0(0x00)부터 255(0xFF)까지의 값을 나타낼 수 있습니다.

참고로 데이터 타입의 char의 값은 ~128 ~ 127이고 byte는 0 ~ 255를 나타내므로 unsigned char와 byte는 같습니다.

ascii코드의 값은 char로 나타냅니다만, 프로그램에서 음수의 값은 필요가 없기 때문에 0~127까지 있고 여기에는 영문 문자가 몇몇의 특수기호를 나타낼 수 있습니다.

byte는 프로그램 상에서 표현되는 가장 최소 단위의 데이터 값입니다.


우리가 영화나 각종 데이터, 프로그램을 말할 때, 몇 기가 바이트라고 표현합니다. 여기서 바이트는 위에서 이야기한 그 byte와 동일하고, 몇 기가 바이트라고 하면 1byte가 2^10(1024)의 단위로 1024b(바이트) = 1kb(키로 바이트), 1024kb (키로 바이트) = 1mb(메가 바이트), 1024mb(메가 바이트) = 1gb(기가 바이트), 1024gb(기가 바이트) = 1tb(테라 바이트) 순의 단위로 변환됩니다.

여담으로 우리가 1테라 바이트 하드 디스크를 사면 2^40b(1099511627776b)의 디스크가 있어야 os에서 정확하게 1tb라고 표시가 됩니다. 그러나 실제 용량을 1000000000000b로 판매를 하니 931.322gb라고 표시가 되는 것입니다.

사실 이게 속임수인데 판매를 할때 1테라 바이트 하드 디스크라고 표시를 하지 않습니다. 1테라 디스크라고 하지.. 즉, 1테라 바이트 디스크라고 하면 허위광고가 되는데 1테라 디스크라 판매가 되서 허위 광고가 아닌 것이 된 것입니다.


그러면 간단하게 우리가 문서나 이미지 파일의 용량을 보면 데이터 크기가 설정되어 있습니다.

png 그림 파일의 용량이 161,656byte입니다. 이 뜻은 161,656개의 byte로 이루어진 데이터라는 소리입니다.

이걸 IO로 읽어 와서 콘솔에 표시하겠습니다.

# IO 모듈
import io;
# byte 단위로 파일 읽기
with open("nowonbuntistory.png", "rb") as handle:
  data = handle.read();
# 콘솔 표시
print(data);

위 이미지를 보시면 「\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x03\x0f\x00\x00\x02\x91\x08\x03\x00...」으로 바이트 단위가 있습니다.

참고로 \x는 16진수 표기법으로 \x89가 하나의 바이트입니다. 그리고 하나의 byte가 125(\x7D)이하의 값은 ascii코드로 표시가 됩니다.

즉 「\x89 P N G \r \n \x1a \n \x00 ...」으로 byte가 이루어져 있습니다.

png의 경우는 이미지 자료 구조인데, 다른 압축 포멧보다 구조체가 단순하긴 합니다만 그래도 단순히 이미지 픽셀 값으로만 이루어진 이미지 포멧이 아니기때문에 자료 구조를 조금 파악해야 합니다.

링크 - https://en.wikipedia.org/wiki/Portable_Network_Graphics


여기서는 제가 자료구조를 파악하려는 글이 아니기 때문에 위 wiki를 참고해 주세요.. 나중에 시간나면 image, move, mp3등 자료 구조도 다 분해해 볼까..

어쨋든 png 파일은 시작이 무조건 「\x89 \x50 \x4E \x47 \x0D \x0A \x1A \x0A」으로 시작합니다.

여기서 \x50 = 80(P), \x4E = 78(N), \x47 = 71(G)의 의미입니다.

링크 - https://ko.wikipedia.org/wiki/ASCII

# byte 구조를 변환하는 모듈
import struct;
# 파일 IO
import io;
# 이미지 파일을 읽어온다.
with open("nowonbuntistory.png", "rb") as handle:
  data = handle.read();
# 처음은 \x89PNG\r\n\x1a\n로 시작하는지 확인한다.
if data[:8] == b'\x89\x50\x4E\x47\x0D\x0A\x1A\x0A':
  # struct.unpack의 결과는 무조건 튜플로 값이 온다. data[8:12]는 data[8], data[9], data[10], data[11]의 의미다.
  junk, = struct.unpack('>L', data[8:12]);
  # junk 값은 13이다.
  print(junk);
  # IHDR를 받는다.
  ihdr, = struct.unpack('>4s', data[12:16]);
  # IHDR를 확인한다.
  print(ihdr.decode('utf-8'));
  # 순서대로 너비, 높이 비트 깊이, 컬러 타입, 압축 메소드, 필터 메소드등을 취득ㅎ나다.
  width, height, bit, type1, type2, type3, type4, pnumber = struct.unpack('>2L5b4xL',data[16:37]);
  print('width :', width, 
        'height :', height, 
        'bit depth', bit, 
        'color type', type1,
        'compression method', type2, 
        'filter method', type3, 
        'interlace method', type4, 
        'palette number', pnumber);

다른 값은 이해가 잘 안되도 width와 height는 대충 알겠습니다.

크기가 대충 맞는 듯합니다.

여기서 struct.unpack은 byte데이터를 변환하는 함수입니다. 파라미터는 지정자와 byte의 값이 들어가게 됩니다.

지정자 설명 최소 바이트
< 리틀 엔디안
> 빅 엔디안
x 1바이트 건너뜀 1
b 부호 있는 바이트(byte) 1
B 부호 없는 바이트(unsigned byte) 1
h 부호 있는 짧은 정수(short) 2
H 부호 없는 짧은 정수(unsigned short) 2
i 부호 있는 정수(int) 4
I 부호 없는 정수(unsigned int) 4
l 부호 있는 긴 정수(long) 4
L 부호 없는 긴 정수(unsigned long) 4
Q 부호 없는 아주 긴 정수 8
f 단정도 부동소수점수 4
d 배정도 부동소수점수 8
p 문자수(count)와 문자 1+count
s 문자 count

위 예에서 가장 긴 지정자라고 하면 '>2L5b4xL'가 있습니다.

빅 엔디안으로 2개의 부호 없는 긴 정수 5개의 부호있는 바이트, 4byte는 건너뛰고 하나의 부호 없는 긴 정수의 뜻입니다.

각각 2+5+1로 8개의 튜플로 된 결과 값이 나오겠네요. 참고로 2L이나 5b는 LL, bbbbb로 표현도 가능합니다. 즉 >LLbbbbbxxxxL 이렇게 표현도 가능한데, 풀어쓰면 헤갈리기 쉽습니다.


이제 반대로 제가 위 이미지의 너비를 바꿔보겠습니다.

# byte 구조를 변환하는 모듈
import struct;
# 파일 IO
import io;
# 이미지 파일을 읽어온다.
with open("nowonbuntistory.png", "rb") as handle:
  data = handle.read();
#byte 타입을 bytearrary 타입으로 변경한다.
barray = bytearray(data);
# 빅엔디안 구조로 부호 없는 긴 정수타입의 1000의 값을 byte로 변경한다.
width = struct.pack('>L',1000);
# 너버의 위치 16에서 20번째까지를 변경한다.
barray[16:20] = width[0:4];
# bytearrary 타입을 byte타입으로 변경한다.
data = bytes(barray);
# 이미지 파일을 작성한다.
with open("nowonbuntistory1.png", "wb") as handle:
  handle.write(data);

그림판으로 열어보면 너비가 783에서 1000으로 변경된 것을 확인할 수 있습니다.

이미지는 픽셀 배열이 뒤틀려서 깨져버렸네요.... 픽셀 배열이 783에 맞추어서 위에서 아래로 왼쪽에서 오른쪽으로 작성되는데 이걸 1000으로 늘려버렸으니 깨져버린 것입니다.


여기서 python에서 byte 타입은 값을 변경할 수 없습니다. bytearray로 변경을 해야 변경이 가능합니다.

이번에는 struct.pack을 사용했습니다. 파라미터는 지정자, 값입니다. 1000의 값을 byte형식으로 바꾼 것입니다.

이걸 이미지 데이터의 너비에 해당하는 영역에 값을 교체했습니다.


개인적으로 C#에서의 Encoding.UTF.GetString과 GetByte랑 많이 비슷하다는 생각이 듭니다.

오히려 파이썬이 좀 더 raw하게 컨트럴 할 수 있다고나 할까? Python 참 매력이 많은 언어입니다.


여기까지 Python에서 byte 타입을 다루는 방법(bytearray, struct 모듈)에 관한 설명이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.