IT이야기

기본 인코딩이 ASCII인데 Python이 유니코드 문자를 인쇄하는 이유는?

cyworld 2022. 4. 6. 21:10
반응형

기본 인코딩이 ASCII인데 Python이 유니코드 문자를 인쇄하는 이유는?

Python 2.6 쉘에서:

>>> import sys
>>> print sys.getdefaultencoding()
ascii
>>> print u'\xe9'
é
>>> 

"e" 문자는 ASCII의 일부가 아니며 인코딩을 지정하지 않았기 때문에 나는 인쇄문 뒤에 횡설수설이나 에러가 있을 것으로 기대했다.기본 인코딩인 ASCII가 무슨 뜻인지 이해가 안 가는 것 같아.

편집

나는 답안 섹션으로 편집을 옮기고 제안된 대로 수락했다.

다양한 답변의 단편적인 부분들 덕분에, 나는 우리가 설명을 붙일 수 있다고 생각한다.

유니코드 문자열 u'\xe9'을 인쇄하려고 시도함으로써 Python은 암시적으로 현재 sys.stdout.encoding에 저장된 인코딩 체계를 사용하여 해당 문자열을 인코딩하려고 시도한다.Python은 실제로 그것이 시작된 환경에서 이 설정을 선택한다.환경에서 적절한 인코딩을 찾을 수 없는 경우, 그 후에만 기본값인 ASCII로 되돌아간다.

예를 들어, 나는 UTF-8로 인코딩하는 bash shell을 사용한다. 여기서 Python을 시작하면 bash shell은 다음 설정을 사용한다.

$ python

>>> import sys
>>> print sys.stdout.encoding
UTF-8

잠시 파이톤 셸에서 벗어나 가짜 인코딩으로 바시의 환경을 설정해 봅시다.

$ export LC_CTYPE=klingon
# we should get some error message here, just ignore it.

그런 다음 파이썬 셸을 다시 시작하고 그것이 정말로 그것의 기본 아스키 인코딩으로 돌아가는지 확인하십시오.

$ python

>>> import sys
>>> print sys.stdout.encoding
ANSI_X3.4-1968

빙고!

이제 아스키 이외의 유니코드 문자를 출력하려고 하면 좋은 오류 메시지가 표시됨

>>> print u'\xe9'
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9' 
in position 0: ordinal not in range(128)

파이톤에서 나와서 배쉬껍질을 버리자.

이제 Python이 문자열을 출력한 후에 어떤 일이 발생하는지 관찰해 봅시다.이를 위해 먼저 그래픽 단자(Gnome Terminal) 내에서 bash shell을 시작하고(나는 Gnome Terminal을 사용함) ISO-8859-1 aka latin-1로 출력을 디코딩하도록 단자를 설정하십시오(그래픽 단자에는 일반적으로 드롭다운 메뉴 중 하나에 문자 인코딩을 설정할 수 있는 옵션이 있음).이것은 실제 셸 환경의 인코딩을 변경하지 않으며, 웹 브라우저와 같이 단말기 자체가 주어진 출력을 디코딩하는 방식만 바꾼다는 점에 유의하십시오.그러므로 당신은 셸의 환경에서 독립적으로 터미널의 인코딩을 변경할 수 있다.그런 다음 셸에서 Python을 시작하여 sys.stdout.encoding이 셸 환경의 인코딩(UTF-8 for me):

$ python

>>> import sys

>>> print sys.stdout.encoding
UTF-8

>>> print '\xe9' # (1)
é
>>> print u'\xe9' # (2)
é
>>> print u'\xe9'.encode('latin-1') # (3)
é
>>>

(1) python은 있는 그대로 바이너리 문자열을 출력하고, 단말기는 이를 수신하여 라틴-1 문자표와 그 값을 일치시키려고 한다.라틴-1, 0xe9 또는 233에서는 "e"라는 문자를 생성하므로 터미널이 이를 표시한다.

(2) python은 현재 sys.stdout.encoding에 설정된 모든 체계를 사용하여 유니코드 문자열을 암시적으로 인코딩하려고 시도하며, 이 경우 "UTF-8"이다. UTF-8 인코딩 후 결과 2진수 문자열은 '\xc3\xa9'이다(나중의 설명 참조).Terminal은 스트림을 이와 같이 수신하고, 라틴-1을 이용하여 0xc3a9를 디코딩하려고 하지만, 라틴-1은 0에서 255로 바뀌기 때문에 스트림을 한 번에 1바이트만 디코딩한다.0xc3a9는 길이가 2바이트로, 따라서 라틴-1 디코더는 0xc3(195), 0xa9(169)로 해석하고, 2개의 문자를 산출한다: WANG와 ©.

(3) python은 라틴-1 체계로 유니코드 코드 포인트 u'\xe9'(233)를 암호화한다.라틴-1 코드 포인트 범위는 0-255이며, 해당 범위 내에서 유니코드와 정확히 동일한 문자를 가리킨다.따라서 해당 범위의 유니코드 코드 포인트는 라틴-1로 인코딩할 때 동일한 값을 산출한다.그래서 라틴-1로 인코딩된 u'\xe9'(233)도 이진 문자열 '\xe9'을 산출한다.Terminal은 그 값을 받아 라틴-1 문자 지도에서 일치시키려 한다.케이스(1)와 마찬가지로 「e」를 산출하고, 그것이 디스플레이 된다.

이제 드롭다운 메뉴에서 터미널의 인코딩 설정을 UTF-8로 변경해 봅시다(예: 웹 브라우저의 인코딩 설정을 변경).Python을 중지하거나 셸을 다시 시작할 필요가 없다.터미널의 인코딩은 이제 파이썬의 인코딩과 일치한다.다시 인쇄해 봅시다.

>>> print '\xe9' # (4)

>>> print u'\xe9' # (5)
é
>>> print u'\xe9'.encode('latin-1') # (6)

>>>

(4) python은 있는 그대로 이진 문자열을 출력한다.터미널은 UTF-8로 그 스트림을 디코딩하려고 시도하지만 UTF-8은 0xe9 값을 이해하지 못해(나중의 설명 참조) 유니코드 코드 포인트로 변환할 수 없다.코드 포인트를 찾을 수 없고 문자가 인쇄되지 않음.

(5) python은 sys.stdout.encoding에 있는 것으로 유니코드 문자열을 암시적으로 인코딩하려고 시도한다.아직 "UTF-8"이야결과 이진 문자열은 '\xc3\xa9'이다.Terminal은 스트림을 수신하고 UTF-8을 사용하여 0xc3a9의 디코딩을 시도한다.유니코드 문자 지도에서 기호 "e"를 가리키는 코드 값 0xe9(233)를 산출한다.터미널에 "e"가 표시됨.

(6) python은 라틴-1로 유니코드 문자열을 인코딩하며 '\xe9' 값이 같은 이진 문자열을 산출한다.다시 말하지만, 터미널의 경우 이것은 사례 (4)와 거의 같다.

결론: - Python은 기본 인코딩을 고려하지 않고 비 유니코드 문자열을 원시 데이터로 출력한다.단말기는 단지 데이터와 현재 인코딩이 일치하면 표시된다. - 파이썬은 sys.stdout.encoding에 지정된 체계를 사용하여 인코딩한 후 유니코드 문자열을 출력한다. - 파이썬은 쉘의 환경에서 해당 설정을 얻는다. - 단말기는 자체 인코딩 설정에 따라 출력을 표시한다. - 단말기의 인코딩은 외설적이다.탄피에서 펜던트.


유니코드, UTF-8 및 라틴-1에 대한 자세한 정보:

유니코드는 기본적으로 일부 키(코드 포인트)가 일부 기호를 가리키도록 관례적으로 할당된 문자표다. 예를 들어 관례에 의해 키 0xe9(233)가 기호 'e'를 가리키는 값이라고 결정되었다.ASCII와 유니코드는 라틴-1과 유니코드 0에서 255까지와 마찬가지로 0에서 127까지의 코드 포인트를 사용한다.즉, ASCII, 라틴-1, 유니코드의 경우 0x41이 'A'를 가리키고, 라틴-1과 유니코드의 경우 'HU'를 가리키며, 0xe9가 'e'를 가리키고 있다.

전자 기기로 작업할 때 유니코드 코드 포인트는 전자적으로 나타낼 수 있는 효율적인 방법이 필요하다.그것이 인코딩 체계에 관한 것이다.다양한 유니코드 인코딩 체계(utf7, UTF-8, UTF-16, UTF-32)가 존재한다.가장 직관적이고 직선적인 인코딩 접근방식은 유니코드 맵에서 코드 포인트의 값을 전자적 형태에 대한 값으로 단순히 사용하는 것이겠지만, 유니코드는 현재 100만개 이상의 코드 포인트를 가지고 있으며, 그 중 일부는 표현하기 위해 3바이트를 필요로 한다는 것을 의미한다.텍스트로 효율적으로 작동하려면 실제 필요와 상관없이 모든 코드 포인트를 문자당 최소 3바이트의 동일한 공간에 저장해야 하기 때문에 1:1 매핑은 다소 비현실적일 수 있다.

대부분의 인코딩 방식에는 공간 요구 사항에 관한 단점이 있으며, 가장 경제적인 인코딩 방식에는 모든 유니코드 코드 포인트가 포함되지 않는다. 예를 들어, aski는 첫 번째 128개만 포함하고, 라틴-1은 첫 번째 256개만 다룬다.좀 더 포괄적이 되려고 노력하는 다른 사람들도 결국 낭비하게 되는데, 왜냐하면 그들은 공통적인 "싸구려" 문자에 대해서도 필요 이상으로 많은 바이트를 요구하기 때문이다.예를 들어 UTF-16은 아스키 범위('B' 65, 여전히 UTF-16에서 2바이트의 스토리지를 필요로 함)를 포함하여 문자당 최소 2바이트를 사용한다.UTF-32는 모든 문자를 4바이트로 저장하기 때문에 훨씬 더 낭비적이다.

UTF-8은 다양한 바이트 공간의 코드 포인트를 저장할 수 있는 체계로 딜레마를 교묘하게 해결했다.그것의 인코딩 전략의 일환으로, UTF-8은 그들의 공간 요구사항과 경계를 (대개 해독할 수 있는) 플래그 비트로 코드 포인트를 묶는다.

아스키 범위(0-127)에서 유니코드 코드 포인트의 UTF-8 인코딩:

0xxx xxxx  (in binary)
  • x는 인코딩 중에 코드 포인트를 "저장"하기 위해 예약된 실제 공간을 보여준다.
  • 선행 0은 이 코드 포인트가 1바이트만 필요함을 UTF-8 디코더에 표시하는 플래그다.
  • 인코딩 시 UTF-8은 특정 범위의 코드 포인트 값을 변경하지 않는다(즉, UTF-8로 인코딩된 65도 65).유니코드와 ASCII도 동일한 범위에서 호환된다는 점을 감안, 우발적으로 UTF-8과 ASCII도 그 범위에서 호환되도록 한다.

예: 'B'에 대한 유니코드 코드 포인트는 이진수로 '0x42' 또는 0100 0010이다(우리가 말했듯이 ASCII에서도 동일).UTF-8로 인코딩하면 다음과 같이 된다.

0xxx xxxx  <-- UTF-8 encoding for Unicode code points 0 to 127
*100 0010  <-- Unicode code point 0x42
0100 0010  <-- UTF-8 encoded (exactly the same)

127개 이상의 유니코드 코드 포인트의 UTF-8 인코딩(비 ASCII):

110x xxxx 10xx xxxx            <-- (from 128 to 2047)
1110 xxxx 10xx xxxx 10xx xxxx  <-- (from 2048 to 65535)
  • 선행 비트 '110'은 2바이트로 인코딩된 코드 포인트의 시작을 UTF-8 디코더에 나타내며, '1110'은 3바이트, 11110은 4바이트 등을 나타낸다.
  • 내부 '10' 플래그 비트는 내부 바이트의 시작을 알리는 데 사용된다.
  • 다시, x는 인코딩 후 유니코드 코드 포인트 값이 저장되는 공간을 표시한다.

예: 'e' 유니코드 코드 포인트는 0xe9(233).

1110 1001    <-- 0xe9

UTF-8이 이 값을 인코딩할 때 값이 127보다 크고 2048보다 작다고 판단하므로 2바이트로 인코딩해야 한다.

110x xxxx 10xx xxxx   <-- UTF-8 encoding for Unicode 128-2047
***0 0011 **10 1001   <-- 0xe9
1100 0011 1010 1001   <-- 'é' after UTF-8 encoding
C    3    A    9

UTF-8 인코딩 후 0xe9 유니코드 코드는 0xc3a9가 된다.그게 바로 터미널이 그걸 받는 방법이야만약 당신의 단말기가 라틴-1(비유니코드 레거시 인코딩 중 하나)을 사용하여 문자열을 디코딩하도록 설정되어 있다면, 당신은 단지 라틴-1의 0xc3가 YANG을 가리키고 0xa9가 YAG를 가리키기 때문에 YANG©을 보게 될 것이다.

유니코드 문자가 stdout으로 인쇄되면sys.stdout.encoding사용된다.유니코드가 아닌 문자는 에 있는 것으로 가정한다.sys.stdout.encoding방금 터미널로 보내졌어내 시스템(Python 2)에서:

>>> import unicodedata as ud
>>> import sys
>>> sys.stdout.encoding
'cp437'
>>> ud.name(u'\xe9') # U+00E9 Unicode codepoint
'LATIN SMALL LETTER E WITH ACUTE'
>>> ud.name('\xe9'.decode('cp437')) 
'GREEK CAPITAL LETTER THETA'
>>> '\xe9'.decode('cp437') # byte E9 decoded using code page 437 is U+0398.
u'\u0398'
>>> ud.name(u'\u0398')
'GREEK CAPITAL LETTER THETA'
>>> print u'\xe9' # Unicode is encoded to CP437 correctly
é
>>> print '\xe9'  # Byte is just sent to terminal and assumed to be CP437.
Θ

sys.getdefaultencoding()파이썬이 다른 옵션을 가지고 있지 않을 때만 사용된다.

Python 3.6 이상에서는 Windows의 인코딩을 무시하고 유니코드 API를 사용하여 터미널에 유니코드를 작성한다는 점에 유의하십시오.UnicodeError 경고가 없으며 글꼴이 지원하는 경우 올바른 문자가 표시된다.폰트가 그것을 지원하지 않더라도, 글자들은 여전히 터미널에서 지원 폰트가 있는 어플리케이션으로 잘라서 붙여질 수 있고 정확할 것이다.업그레이드!

Python RET는 사용자 환경에서 사용할 인코딩을 선택하려고 한다.만약 제정신을 찾으면 모든 게 제대로 작동한다.무슨 일이 일어나고 있는지 알 수 없을 때 그것은 버그아웃된다.

>>> print sys.stdout.encoding
UTF-8

명시적 유니코드 문자열을 입력하여 인코딩을 지정하셨습니다.사용하지 않은 결과 비교u접두사를 붙이다

>>> import sys
>>> sys.getdefaultencoding()
'ascii'
>>> '\xe9'
'\xe9'
>>> u'\xe9'
u'\xe9'
>>> print u'\xe9'
é
>>> print '\xe9'

>>> 

의 경우\xe9그러면 파이톤은 당신의 기본 인코딩(Ascious)을 가정하고, 따라서 ...을 인쇄한다... 빈칸.

나한테는 효과가 있어:

import sys
stdin, stdout = sys.stdin, sys.stdout
reload(sys)
sys.stdin, sys.stdout = stdin, stdout
sys.setdefaultencoding('utf-8')

Python default/implicit 문자열 인코딩변환에 따라:

  • 언제print잉잉unicode, 입니다.encode을 가지고 있다.<file>.encoding.
    • …할 때encoding아직 정해지지 않았다.unicode은연중에 로 전환되다str(그것을 위한 코덱이sys.getdefaultencoding(), 즉.ascii, 어떤 국민적인 문자들이 ,UnicodeEncodeError)
    • 표준 하천에 대해서는encoding환경으로부터 유추된다.그것은 일반적으로 fot로 설정된다.tty(단자의 로케일 설정에서) 스트림이지만 파이프에 대해 설정되지 않을 가능성이 있음
      • 비상대기상태print u'\xe9'출력이 단자에 도달하면 성공할 가능성이 높고, 리디렉션되면 실패할 가능성이 높다.해결책은 이다.encode()앞에 원하는 인코딩이 있는 문자열print잉잉
  • 언제print잉잉str, 바이트는 있는 그대로 스트림에 보내진다.단자가 보여주는 글립스는 위치 설정에 따라 달라진다.

참조URL: https://stackoverflow.com/questions/2596714/why-does-python-print-unicode-characters-when-the-default-encoding-is-ascii

반응형