ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [파이썬] str, bytes, bytearray
    Python/이것저것 파이썬 2022. 8. 15. 12:25
    반응형
    결론:

    1. bytes는 문자열(str)을 숫자(byte)로 다룹니다.
    즉, bytes는 문자열(str)을 숫자(byte) 뭉치로 바꾼 것입니다.

    2. 한글을 써야하는 상황에서
    bytes를 직접 다룰 일은 많지 않습니다.
    bytes를 디코딩을 거쳐 문자열(str)로 변환한 뒤,
    문자열(str)을 다루면 됩니다.

     

    0. 미리 알아야 할 것

     

    ASCII 코드

    ASCII (American Standard Code for Information Interchange, 미국 정보 교환 표준 부호)

    000(0x00)부터 127(0x7F)까지 총 128개의 부호가 사용된다.
    1바이트를 구성하는 8비트(=1바이트) 중에서 7비트만 쓰도록 제정된 이유는,
    나머지 1비트를 통신 에러 검출을 위한 용도로 비워두었기 때문이다.

     

    한글 코드의 역사

    https://d2.naver.com/helloworld/19187

    http://www.munhwa.com/news/view.html?no=2022011001031612000001

    https://namu.wiki/w/%EC%99%84%EC%84%B1%ED%98%95

    https://namu.wiki/w/%EC%A1%B0%ED%95%A9%ED%98%95

    간략하게 요점만 정리한다면...

    한글 코드는
    그 구현 원리에 따라
    크게 조합형과 완성형으로 분류합니다.

    (80년대 한창 코드가 난립할 때는
    27종 이상의 한글 코드가 있었습니다.)

    정부는
    2바이트 '완성형' 방식을 표준으로 정하고
    후에 2바이트 '조합형' 방식을 보조 표준으로 정했습니다.

    MS 윈도 또한 정부 표준인 완성형 한글 코드를 채택했기 때문에
    완성형 한글 코드가 대부분의 영역에서 사용되었습니다.

    (반면, '한컴(한글과컴퓨터)'의
    아래아한글 워드 프로세서는
    조합형 한글을 사용했습니다.)

    초기 완성형 한글(EUC-KR)에는
    한글 2350자가 포함되어 있었습니다.

    (턱없이 부족한 글자 수입니다.
    설렜어의 '렜'이 없었어요.)

    윈도 95가 발매되면서
    ECU-KR을 확장해
    11,172자의 한글이 포함된
    통합(확장) 완성형(CP949) 체계가
    사용됩니다.

    CP949는
    EUC-KR의 남는 공간에
    한글을 (우겨) 넣어서
    (추가된) 한글의 사전적 순서와
    코드의 순서가 다른
    단점이 있었습니다.

    참고로 자바의 CP949는
    IBM에서 정의한 CP949이며
    ECU-KR과 유사합니다.
    자바에서는 별도의 MS949가 있습니다.
    https://namu.wiki/w/CP949

    한글 코드의 변천사를 보면,
    유니코드 만세라는 생각만...

     

    유니 코드

    전 세계의 모든 문자를 다루도록 설계된 표준 문자 전산 처리 방식.
    유니코드 표의 0번부터 127번까지는 아스키코드와 일치합니다.
    (=유니코드는 아스키코드의 슈퍼셋입니다.)

    유니코드가 없었을 때는
    하나의 문서에서
    여러 가지 외국어를
    동시에 지원하기가 힘들었습니다.

    완성형 한글 코드에는 영문자, 유럽 문자(?), 한글, 일본어, 한자 등등이 포함되어 있었는데요.
    자세한 것은 아래 링크 참고..
    https://namu.wiki/w/%EC%99%84%EC%84%B1%ED%98%95/%ED%8A%B9%EC%88%98%20%EB%AC%B8%EC%9E%90
    여기에 포함되어 있지 않은 문자와 한글을 동시에 쓰기가 너무 힘들었죠.

     

    ord() 내장 함수

    ord(문자)는 각 글자의 유니코드(아스키코드) 값을 리턴합니다.

    print('a:', ord('a'))
    # a: 97
    
    print('가:', ord('가'))
    # 가: 44032

     

    chr() 내장 함수

    chr(코드)은 코드에 해당하는 문자를 리턴합니다.

    print('44033:', chr(44033))
    # 44033: 각

     

     

    1. bytes 만들기

    영문자로 된 문자열(str)에 'b'를 붙이면 bytes 객체가 됩니다.

    print(type(b'abc'))  # <class 'bytes'>

     

    뒤에 좀 더 자세히 다루겠지만
    '문자열(str)을 인코딩'해서
    bytes 객체를 만들 수 있습니다.

    print('abc'.encode())  # b'abc'
    print(bytes('abc', 'utf-8'))  # b'abc'

     

    리스트나 튜플 등의 '반복 가능한 객체'에
    아스키 코드를 넣고,
    bytes로 변환 해도 됩니다.

    bytes(반복_가능한_객체)

    print(bytes([65, 66, 67]))  # b'ABC'
    
    print(bytes(each for each in range(128)))  # 제너레이터 익스프레션을 사용했습니다. 
    print(bytes(range(128)))  # range는 제너레이터죠?
    # 0~127의 아스키 코드를 볼 수 있습니다.
    # b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f'

     

     

    2. 1byte는 8bit

    1byte는 8bit입니다.
    8비트는 0~255까지 숫자를 표현할 수 있습니다.

    아스키코드는 7bit(0~127)만 사용하는 것도 잊지 맙시다.

     

    bytes는 아스키 리터럴 문자만 담을 수 있습니다.
    한글을 담으면 에러가 발생합니다.
    (뒤에 자세히 다루겠지만 한글을 담기 위해서는 인코딩이 필요합니다.)

    print(b'한글')
    # SyntaxError: bytes can only contain ASCII literal characters

     

     

    3. 문자열(str)과 bytes 뜯어보기

    (당연한 이야기지만) '문자열(str)'을 하나씩 뜯어보면 '문자'가 나옵니다.

    str_a = 'abc'
    for each in str_a:
        print(each)
    '''
    a
    b
    c
    '''

    반면, 'bytes'를 하나씩 뜯어보면 'byte(숫자)'가 나옵니다.

    bytes_a = b'abc'
    
    for each in bytes_a:
        print(each)
    '''
    97
    98
    99
    '''

     

     

    4. immutable한 bytes

    파이썬에서
    문자열(str)이 불변(immutable)이듯,
    bytes도 불변입니다.

    bytes를
    byte(숫자)가 들어있는
    튜플로 생각하셔도 좋습니다.
    이 글에서는 숫자 뭉치라고 표현했습니다.

    특정 byte를 바꾸려고 하면
    에러가 발생합니다.

    bytes_b = b'b'
    bytes_b[0] = 97
    # 'bytes' object does not support item assignment

     

     

    5. mutable한 bytes는 bytearray

    파이썬에는 bytearray라는 것이 있습니다.
    변경 가능한(mutable) bytes입니다.

    temp_bytearray = bytearray(b'abc')  # bytes를 bytearray로 변환합니다. 
    print(temp_bytearray[0])  # 97
    temp_bytearray[1] = 102
    print(temp_bytearray)  # bytearray(b'afc')
    
    # bytearray를 bytes로 변환합니다. 
    print(bytes(temp_bytearray))  # b'afc'

     

    예제) bytes_a의 각 문자(b'abc')를 알파벳 순서 하나 뒤로 바꿔봅시다.

    b'abc'를 b'bcd'로 변환해 봅시다.

    배운 것을 골고루 써본다면 이런 코드를...

    bytes_a = b'abc'
    temp_bytearray = bytearray()  # 빈 바이트 어레이를 생성
    for each in bytes_a:
        temp_bytearray.append(each + 1)
    print(temp_bytearray)  # bytearray(b'bcd')
    result = bytes(temp_bytearray)  # bytearray를 bytes로 변환합니다.
    print(result)  # b'bcd' 가 출력됩니다.

    한 줄 로 간단하게는...

    bytes_a = b'abc'
    print(bytes(each + 1 for each in bytes_a))  # b'bcd' 가 출력됩니다.

     

     

    6. encode

    이제 문자열(str)을 bytes(숫자 뭉치)로 변환해 봅시다.

    문자열(str)을 bytes로 바꾸는 것을 인코딩이라고 합니다.
    사전에서 encoding을 찾아보면 '부호화'입니다.
    동영상 인코딩이라는 표현을 들어보셨을 겁니다.
    동영상을 다른 코덱(= 다른 압축 방식, 다른 부호화 방식)으로 변환하는 것을 인코딩이라고 하죠.

    동영상에 여러 코덱이 있듯
    한글 코드(인코딩)도 여러 가지 방식이 있습니다.

    파이썬에서 지원하는 인코딩 방식은 다음 주소에서 확인할 수 있습니다.
    https://docs.python.org/3.10/library/codecs.html#standard-encodings

    파이썬에서 지원하는 '한글' 인코딩은 cp949, euc_kr, johab 등이 있습니다.

    * 한글 코드의 역사에서 각각을 설명했습니다.

     

    파이썬 3 내부적으로는 UTF-8 유니코드를 사용합니다.

    * UTF는 “Unicode Transformation Format”의 약자이며 ‘8’은 8비트 값이 인코딩에 사용됨을 뜻합니다.
    * 파이썬 2에서는 유니코드를 기본적으로 지원하지 않아서 한글 표현이 상당히 불편했습니다.

     

    파이썬에서 문자열(str)을 bytes로 변환할 때는
    bytes() 내장 함수를 이용하거나,
    문자열(str)encode() 메서드를 사용합니다.

    bytes()로 변환할 때는 인코딩(어떤 코드표를 이용할 것인지)을 꼭 지정해야 합니다.

    print(bytes('abc', 'utf_8'))  # b'abc'

     

    encode() 메서드로 변환할 때
    인코딩을 지정하지 않으면
    UTF-8(가장 많이 사용되는 유니코드 인코딩)로 인코딩 됩니다.

    print('abc'.encode())  # b'abc'
    print('abc'.encode('utf_8'))  # b'abc'

     

    한글 '가'를 UTF-8로 변환해 보겠습니다.
    b'\xea\xb0\x80'로 변환이 됩니다.
    3바이트를 사용합니다.

    print('가'.encode())  # b'\xea\xb0\x80'
    print(len('가'.encode()))  # 3

     

     

    7. decode

    b'\xea\xb0\x80'을 문자열(str)로 디코딩해보겠습니다.

    decode()를 이용할 때는 디코딩을 생략할 수 있습니다.
    생략하면 UTF-8(가장 많이 사용되는 유니코드 인코딩)로 디코딩됩니다.

    print(b'\xea\xb0\x80'.decode())  # '가'
    print(str(b'\xea\xb0\x80', 'utf-8'))  # '가'
    print(str(b'\xea\xb0\x80'))  # b'\xea\xb0\x80'

     

     

    8. 한글의 인코딩과 디코딩

    유니코드 이전 한글 코드 중
    가장 많이 사용되었던 것은 KS 완성형 한글입니다.
    초기 완성형인 euc_kr,
    통합(확장) 완성형인 cp949에서 '가'는 같은 코드값을 가집니다. (당연히)

    print('가'.encode('euc_kr'))  # b'\xb0\xa1'
    print(len('가'.encode('euc_kr')))  # 2
    print(b'\xb0\xa1'.decode('euc_kr'))  # '가'
    
    print('가'.encode('cp949'))  # b'\xb0\xa1'

     

    초기 완성형(euc_kr)에 없는 글자인 '렜'은 어떻게 인코딩 될까요?

    먼저 '렛'을 포함하고 있는 통합(후기) 완성형(cp949)으로 인코딩, 디코딩해보았습니다.
    2바이트로 정상적으로 인코딩, 디코딩됩니다.

    print('렜'.encode('cp949'))  # b'\x8e\xae'
    print(len('렜'.encode('cp949')))  # 2
    print(b'\x8e\xae'.decode('cp949'))  # '렜'

     

    초기 완성형(euc_kr)으론 재미있는 결과가 나옵니다.
    인코딩과 디코딩은 정상적으로 됩니다만,
    무려 8바이트의 bytes가 나왔는데요.

    print('렜'.encode('euc_kr'))  # b'\xa4\xd4\xa4\xa9\xa4\xc4\xa4\xb6'
    print(len('렜'.encode('euc_kr')))  # 8
    print(b'\xa4\xd4\xa4\xa9\xa4\xc4\xa4\xb6'.decode('euc_kr'))  # '렜'

     

    앞의 2바이트(\xa4\xd4)를 제외하고 나머지를 디코딩해봅시다.

    print(b'\xa4\xa9\xa4\xc4\xa4\xb6'.decode('euc_kr'))  # 'ㄹㅔㅆ'

    'ㄹㅔㅆ' 이 (정상적으로???) 들어있네요.

    완성형에서는 '한글채움문자(A4D4)'가 있습니다.
    파이썬은 이를 활용해 정상적으로 한글을 변환했습니다.

    KS X 1001 완성형의 문자 중 하나로, 이름은 ‘한글 채움 문자’이다. 완성형 2350자로 나타낼 수 없는 한글을 표현하기 위해 존재하는 문자이다.

    KS X 1001은 완성형 2350자에 없는 한글을 저 한글 채움 문자 + 낱자 셋으로 나타내도록 규정하고 있다.
    종성이 없을 경우 종성 자리에 한글 채움 문자를 또 넣는다.

    예를 들어 ‘뷁’은 ‘[채움]ㅂㅞㄺ’(EUC-KR: A4D4 A4B2 A4CE A4AA)로 나타내고,
    ‘쓔’는 ‘[채움]ㅆㅠ[채움]’(EUC-KR: A4D4 A4B6 A4D0 A4D4)로 나타낸다.

    하지만 구현하는 방법과 입력하는 방법이 번거로워
    이것을 실제로 지원하는 환경은 Mozilla Firefox 등 극소수의 환경밖에 없었으며....

    https://namu.wiki/w/%ED%95%9C%EA%B8%80%20%EC%B1%84%EC%9B%80%20%EB%AC%B8%EC%9E%90

     

     

    9. 제가 생각하는 str, bytes, bytearray

    파이썬에서는 문자열 처리에 str, bytes, bytearray를 사용할 수 있습니다만,
    일반적인 상황에서는 str을 사용하는 것이 합리적입니다.

    우리는 한글을 처리해야하기 때문입니다.

    UTF-8은 가변 인코딩방식입니다.
    'a'는 1 byte이고 '가'는 3 byte입니다.

    완성형 한글의 한글채움문자 또한
    일종의 가변 인코딩입니다. ㄷㄷㄷ

    가변 인코딩류는
    for 문을 돌릴 때부터
    한 글자가 어디까지인가
    고민을 해야하기 때문에..
    bytes로는 답이 없습니다.

    당연한 이야기지만 한 글자가 아니라 한 바이트 단위로 처리됩니다.

    for each in '렜'.encode('euc-kr'):
        print(each)
    '''
    164
    212
    164
    169
    164
    196
    164
    182
    '''

     

    str은 한 글자씩 잘 나눠집니다.

    for each in '설렜어':
        print(each)
    '''
    설
    렜
    어
    '''

     

    파이썬 외부의 문자열은
    bytes(숫자 뭉치) 형태로
    파이썬에 들어옵니다.

    (특히 한국어 환경에서는)
    이를 str로 디코딩해야
    불편없이 사용할 수 있습니다.

    (특별한 이유가 없다면)
    bytes는 디코딩 전의 임시적인 수단
    생각하는 게 합리적일 것 같습니다.

    반응형
Designed by Tistory.