Python

struct 라이브러리

퇴근마렵네 2023. 2. 3. 01:18

파이썬에서는 bytes 데이터를 struct 라는 라이브러리로 쉽게 파싱할 수 있습니다.

파이썬 기본 모듈이기 때문에 별도의 설치과정이 필요 없습니다.

 

struct는 기본적으로 pack unpack이라는 메소드가 있습니다.

pack은 원하는 값들을 특정 포맷에 따라 하나의 바이트 객체로 합쳐주는 함수고

unpack은 하나의 바이트 객체를 특정 포맷에 따라 여러 개의 바이트 객체로 분리해주는 함수입니다.

 

1. pack

 

import struct

byte = struct.pack('b', 3)
print(byte)
>>> b'\x03'

 

pack 메소드에는 두가지 파라미터가 필요합니다.

첫번째 파라미터는 format 문자열이고.

두번째 파라미터는 바이트로 변환할 값입니다.

 

만약 숫자 3을 1바이트로 패킹하고 싶으면

첫번째 파라미터에는 'b'를, 두번째 파라미터에는 3을 적어주시면 됩니다.

결과창에서 \x는 16진수를 의미합니다. 즉, \x03 = 0x03입니다.

 

format 문자열로 쓸 수 있는 포맷은 다음과 같습니다.

포맷 타입 설명
b signed char 부호 있는 1bytes 정수
B unsigned char 부호 없는 1bytes 정수
h short 부호 있는 2bytes 정수
H unsigned short 부호 없는 2bytes 정수
i int 부호 있는 4bytes 정수
I unsigned int 부호 없는 4bytes 정수
l long 부호 있는 4bytes 정수
L unsigned long 부호 없는 4bytes 정수
q long long 부호 있는 8bytes 정수
Q unsigned long long 부호 없는 8bytes 정수
f float 부호 있는 4bytes 실수
d double 부호 있는 8bytes 실수
c char 길이가 1인 문자열 (1bytes)
s char[] 여러 길이의 문자열
? boolean True / False

자세한 건 https://python.flowdas.com/library/struct.html#format-characters 참고 바랍니다.

 

byte = struct.pack('b', 3) # 1bytes 부호 있는 정수
print(byte)
>>> b'\x03'  # 0x03

byte = struct.pack('h', 3)  # 2bytes 부호 있는 정수
print(byte)
>>> b'\x03\x00'  # 0x0003

byte = struct.pack('l', 3)  # 4bytes 부호 있는 정수
print(byte)
>>> b'\x03\x00\x00\x00'  # 0x00000003

byte = struct.pack('L', 3)  # 4bytes 부호 없는 정수
print(byte)
>>> b'\x03\x00\x00\x00'  # 0x00000003

 

동일한 숫자 3을 포맷 형식에 따라 1바이트로도, 2바이트로도, 4바이트로도 나타낼 수 있습니다.

자세히 보면 가장 밑에 l과 L은 결과값이 똑같습니다.

하지만 음수인 -3을 패킹한다면

 

byte = struct.pack('l', -3)  # 4bytes 부호 있는 정수
print(byte)
>>> b'\xfd\xff\xff\xff'

byte = struct.pack('L', -3)  # 4bytes 부호 없는 정수
print(byte)

>>> Traceback (most recent call last):
    File "d:\Users\Desktop\Python\audio\wav_file.py", line 19, in <module>
    byte = struct.pack('L', -3)
    struct.error: argument out of range

 

'L'의 경우 에러가 뜹니다. 'L'은 부호 없는 정수만 허용되기 때문입니다.

그런데 아까 위의 결과값에서 이상한걸 못 느꼈나요?

struct.pack('h', 3) = 0x0003인데 표기는 \x03 \x00으로 표기됩니다.

즉, 기본적으로 표기 방식이 리틀 엔디언(little endian)으로 표기됩니다.

그럼 빅 엔디언(big endian)으로 표기하려면 어떻게 해야할까요?

 

byte = struct.pack('>h', 3)  # 2bytes 부호 있는 정수 (big endian)
print(byte)
>>> b'\x00\x03'  # 0x0003

 

포맷 형식 맨 앞에 '>' 기호를 추가하면 됩니다.

리틀 엔디언은 반대방향으로 '<' 기호를 쓰거나 생략하면 됩니다.

화살표 방향으로 표기한다. 라고 생각하면 헷갈리지 않습니다.

 

여러개의 데이터를 패킹할 수도 있습니다.

byte = struct.pack('>bhi', 1, 2, 3)
print(byte)
>>> b'\x01\x00\x02\x00\x00\x00\x03'

byte = struct.pack('<bhi', 1, 2, 3)
print(byte)
>>> b'\x01\x02\x00\x03\x00\x00\x00'

byte = struct.pack('>h3sc', 1, b'abc', b'z')
print(byte)
>>> b'\x00\x01abcz'

 

포맷 형식을 연이어서 적어주고 값들도 차례대로 적어주면 됩니다.

b'\x01\x00\x02\x00\x00\x00\x03'  -> 1 ('>b', 1bytes)

b'\x01\x00\x02\x00\x00\x00\x03'  -> 2 ('>h', 2bytes)

b'\x01\x00\x02\x00\x00\x00\x03'  -> 3 ('>i', 4bytes)

 

b'\x01\x02\x00\x03\x00\x00\x00'  -> 1 ('<b', 1bytes)

b'\x01\x02\x00\x03\x00\x00\x00'  -> 2 ('<h', 2bytes)

b'\x01\x02\x00\x03\x00\x00\x00'  -> 3 ('<i', 4bytes)

 

b'\x00\x01abcz'  -> 1 (>'h', 2bytes)

b'\x00\x01abcz'  -> b'abc' ('3s', 3bytes)

b'\x00\x01abcz'  -> b'z' ('c', 1bytes)

 

 

2. unpack

다음은 unpack 함수입니다.

pack의 역순이기 때문에 pack만 이해하신다면 쉽습니다.

 

unpack = struct.unpack('b', b'\x03')
print(unpack)

>>> (3,)

 

unpack도 pack과 마찬가지로 두가지 파라미터가 필요합니다.

첫번째 파라미터는 format 문자열이고.

두번째 파라미터는 bytes 객체입니다.

언팩 결과는 튜플로 반환됩니다.

 

unpack = struct.unpack('<h', b'\x03\x00')
print(unpack)

unpack = struct.unpack('>h', b'\x00\x03')
print(unpack)

unpack = struct.unpack('<i', b'\x03\x00\x00\x00')
print(unpack)

unpack = struct.unpack('>i', b'\x00\x00\x00\x03')
print(unpack)

>>> (3,)
    (3,)
    (3,)
    (3,)

 

똑같은 3을 엔디언 방식, 바이트 크기 별로 다르게 해서 언팩한 결과입니다.

 

 

만약 포맷 형식과 바이트 크기가 다르면 에러가 발생합니다.

unpack = struct.unpack('>i', b'\x00\x00\x03')
print(unpack)

>>> struct.error: unpack requires a buffer of 4 bytes

 

'i'는 4바이트 크기의 데이터가 필요한데 3바이트만 적었기 때문에 오류가 발생했습니다.

그래서 언팩시에는 반드시 바이트 크기를 맞춰줘야합니다.

 

 

패킹과 마찬가지로 여러개의 데이터를 언패킹할 수 있습니다.

unpack = struct.unpack('h4sc', b'\x01\x00abcdz')
print(unpack)
>>> (1, b'abcd', b'z')