기본 인코딩이 ASCII 일 때 파이썬이 유니 코드 문자를 인쇄하는 이유는 무엇입니까?


139

파이썬 2.6 쉘에서 :

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

"é"문자가 ASCII의 일부가 아니고 인코딩을 지정하지 않았기 때문에 print 문 뒤에 약간의 횡설수설 또는 오류가있을 것으로 예상됩니다. ASCII가 기본 인코딩이라는 것이 무엇인지 이해하지 못하는 것 같습니다.

편집하다

수정 사항을 답변 섹션 으로 옮기고 제안대로 수락했습니다.


6
편집 내용 을 답변으로 바꾸고 받아 들일 수 있다면 꽤 좋을 것입니다.
mercator

2
'\xe9'UTF-8 용으로 구성된 터미널에서 인쇄 하면 인쇄 되지 않습니다é . \xe9유효한 UTF-8 시퀀스가 ​​아니기 때문에 대체 문자 (대개 물음표)를 인쇄합니다 ( 앞의 바이트 다음에 오는 2 바이트가 없음). 그것은 확실히 라틴어 -1로 해석 되지 않을 것입니다.
Martijn Pieters

2
@MartijnPieters \xe9인쇄 출력 할 때 터미널이 ISO-8859-1 (latin1)로 디코딩되도록 지정된 부분을 훑어 보았을 것 é입니다.
Michael Ekoka

2
아 그래, 나는 그 부분을 그리워했다. 터미널은 쉘과 다른 구성입니다. 검사.
Martijn Pieters

나는 대답을 훑어 보았지만 실제로 파이썬 2.7의 u 접두사가없는 문자열이 있습니다. 왜 여전히 유니 코드로 처리됩니까? (my sys.getdefaultencoding ()은 ascii입니다)
dtc

답변:


104

다양한 답변의 비트와 조각 덕분에 우리는 설명을 할 수 있다고 생각합니다.

파이썬은 유니 코드 문자열 u '\ xe9'를 인쇄하려고하여 현재 sys.stdout.encoding에 저장된 인코딩 체계를 사용하여 해당 문자열을 암시 적으로 인코딩하려고합니다. 파이썬은 실제로 시작된 환경에서이 설정을 선택합니다. 환경에서 적절한 인코딩을 찾을 수없는 경우에만 기본 ASCII (ASCII)로 되돌 립니다.

예를 들어 인코딩 기본값은 UTF-8 인 bash 셸을 사용합니다. 파이썬을 시작하면 그 설정을 받아 사용합니다.

$ python

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

잠시 파이썬 셸을 종료하고 가짜 인코딩으로 bash 환경을 설정합시다.

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

그런 다음 파이썬 쉘을 다시 시작하고 실제로 기본 ASCII 인코딩으로 되 돌리는 지 확인하십시오.

$ python

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

빙고!

이제 ASCII 외부에서 유니 코드 문자를 출력하려고하면 멋진 오류 메시지가 표시됩니다

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

파이썬을 끝내고 bash 쉘을 버릴 수 있습니다.

이제 파이썬이 문자열을 출력 한 후 어떤 일이 발생하는지 살펴 보겠습니다. 이를 위해 먼저 그래픽 터미널 내에서 bash 쉘을 시작하고 (Gnome Terminal을 사용합니다) ISO-8859-1 aka latin-1 (그래픽 터미널은 일반적으로 문자 설정 옵션이 있습니다) 드롭 다운 메뉴 중 하나에서 인코딩 ). 이것은 실제 쉘 환경의 인코딩을 변경하지 않으며 , 터미널 자체가 웹 브라우저와 약간 다르게 주어진 출력을 디코딩 하는 방식 만 변경합니다 . 따라서 쉘 환경에서 터미널 인코딩을 독립적으로 변경할 수 있습니다. 그런 다음 셸에서 Python을 시작하고 sys.stdout.encoding이 셸 환경의 인코딩 (UTF-8)으로 설정되어 있는지 확인하십시오.

$ python

>>> import sys

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

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

(1) 파이썬은 그대로 이진 문자열을 출력하고, 터미널은 그것을 받아 라틴 -1 문자 맵과 그 값을 일치 시키려고 시도합니다. latin-1에서 0xe9 또는 233은 문자 "é"를 생성하므로 터미널이 표시됩니다.

(2) python 은 현재 sys.stdout.encoding에 설정된 체계를 사용하여 유니 코드 문자열 을 암시 적으로 인코딩 하려고 시도합니다 .이 경우 "UTF-8"입니다. UTF-8 인코딩 후 결과 이진 문자열은 '\ xc3 \ xa9'입니다 (나중에 설명 참조). 터미널은 이와 같이 스트림을 수신하고 latin-1을 사용하여 0xc3a9를 디코딩하려고 시도하지만 latin-1은 0에서 255로 이동하므로 스트림은 한 번에 1 바이트 만 디코딩합니다. 0xc3a9는 2 바이트 길이이므로 latin-1 디코더는이를 0xc3 (195) 및 0xa9 (169)로 해석하여 Ã 및 ©의 두 문자를 생성합니다.

(3) 파이썬은 라틴 코드 체계로 유니 코드 코드 포인트 u '\ xe9'(233)를 인코딩합니다. 라틴 -1 코드 포인트 범위는 0-255이며 해당 범위 내에서 유니 코드와 정확히 동일한 문자를 가리 킵니다. 따라서 해당 범위의 유니 코드 코드 포인트는 latin-1로 인코딩 될 때 동일한 값을 생성합니다. 따라서 라틴 -1로 인코딩 된 u '\ xe9'(233)는 이진 문자열 '\ xe9'도 생성합니다. 터미널은이 값을 받아서 latin-1 문자 맵에서 일치 시키려고합니다. 사례 (1)과 마찬가지로 "é"가 표시되고 이것이 표시됩니다.

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

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

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

>>>

(4) 파이썬은 그대로 이진 문자열을 출력합니다 . 터미널은 UTF-8을 사용하여 해당 스트림을 디코딩하려고 시도합니다. 그러나 UTF-8은 0xe9 값을 이해하지 못하므로 (나중에 설명 참조) 유니 코드 코드 포인트로 변환 할 수 없습니다. 코드 포인트가없고 문자가 인쇄되지 않습니다.

(5) 파이썬 은 sys.stdout.encoding에있는 것으로 유니 코드 문자열 을 암시 적으로 인코딩 하려고 시도합니다 . 여전히 "UTF-8"입니다. 결과 이진 문자열은 '\ xc3 \ xa9'입니다. 터미널은 스트림을 수신하고 UTF-8을 사용하여 0xc3a9를 디코딩하려고 시도합니다. 유니 코드 문자 맵에서 "é"기호를 가리키는 코드 값 0xe9 (233)를 반환합니다. 터미널에 "é"가 표시됩니다.

(6) 파이썬은 라틴 -1로 유니 코드 문자열을 인코딩하면 동일한 값 '\ xe9'의 이진 문자열을 생성합니다. 다시 말하지만, 터미널의 경우는 케이스 (4)와 거의 동일합니다.

결론 :-파이썬은 기본 인코딩을 고려하지 않고 비 유니 코드 문자열을 원시 데이터로 출력합니다. 터미널은 현재 인코딩이 데이터와 일치하는 경우 이들을 표시합니다. -Python은 sys.stdout.encoding에 지정된 체계를 사용하여 인코딩 한 후 유니 코드 문자열을 출력합니다. -파이썬은 셸 환경에서 해당 설정을 가져옵니다. -터미널은 자체 인코딩 설정에 따라 출력을 표시합니다. -터미널의 인코딩은 쉘의 인코딩과 독립적입니다.


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

유니 코드는 기본적으로 일부 키 (코드 포인트)가 일부 심볼을 ​​가리 키도록 할당 된 문자 표입니다. 예를 들어, 일반적으로 키 0xe9 (233)는 'é'기호를 가리키는 값으로 결정되었습니다. ASCII와 유니 코드는 0에서 127까지의 동일한 코드 포인트를 사용합니다 (라틴 -1 및 유니 코드는 0에서 255까지). 즉, 0x41은 ASCII의 경우 A를 가리키고, 라틴 -1 및 유니 코드는 0xc8은 'Ü'을 가리 킵니다. latin-1 및 Unicode, 0xe9는 latin-1 및 Unicode에서 'é'를 가리 킵니다.

전자 장치로 작업 할 때 유니 코드 코드 포인트는 전자적으로 표현할 수있는 효율적인 방법이 필요합니다. 그것이 인코딩 체계에 관한 것입니다. 다양한 유니 코드 인코딩 체계 (utf7, UTF-8, UTF-16, UTF-32)가 있습니다. 가장 직관적이고 직접적인 인코딩 방식은 유니 코드 맵의 코드 포인트 값을 전자 형식의 값으로 사용하는 것이지만 현재 유니 코드에는 백만 개 이상의 코드 포인트가 있으므로 일부는 3 바이트가 필요합니다. 표현했다. 텍스트를 효율적으로 사용하려면 1 대 1 매핑은 실제 요구 사항에 관계없이 모든 코드 포인트를 문자 당 최소 3 바이트로 정확히 동일한 공간에 저장해야하기 때문에 다소 비실용적입니다.

대부분의 인코딩 체계에는 공간 요구 사항에 대한 단점이 있으며, 가장 경제적 인 것은 모든 유니 코드 코드 포인트를 다루지는 않습니다. 예를 들어 ascii는 첫 128 개만 다루고 라틴 -1은 첫 256 개만 다루고 있습니다. 일반적인 "저렴한"문자라도 필요한 것보다 많은 바이트가 필요하기 때문에 낭비입니다. 예를 들어 UTF-16은 ASCII 범위의 문자를 포함하여 문자 당 최소 2 바이트를 사용합니다 ( 'B'는 65이지만 여전히 UTF-16에서 2 바이트의 저장 공간이 필요합니다). UTF-32는 모든 문자를 4 바이트로 저장하므로 훨씬 더 낭비입니다.

UTF-8은 다양한 양의 바이트 공간으로 코드 포인트를 저장할 수있는 체계로 딜레마를 영리하게 해결했습니다. 인코딩 전략의 일환으로 UTF-8은 공간 요구 사항과 경계를 나타내는 플래그 비트로 코드 포인트를 묶습니다 (아마도 디코더에).

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

0xxx xxxx  (in binary)
  • x는 인코딩하는 동안 코드 포인트를 "저장"하도록 예약 된 실제 공간을 보여줍니다.
  • 선행 0은 UTF-8 디코더에이 코드 포인트에 1 바이트 만 필요함을 나타내는 플래그입니다.
  • 인코딩시 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'은 UTF-8 디코더에 2 바이트로 인코딩 된 코드 포인트의 시작을 나타내고 '1110'은 3 바이트를 나타내고 11110은 4 바이트 등을 나타냅니다.
  • 내부 '10'플래그 비트는 내부 바이트의 시작을 알리는 데 사용됩니다.
  • 다시, x는 인코딩 후 유니 코드 코드 포인트 값이 저장되는 공간을 표시합니다.

예를 들어 'é'유니 코드 코드 포인트는 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이 Ã 및 0xa9에서 ©를 가리 키기 때문에 Ã ©가 표시됩니다.


6
훌륭한 설명. 이제 UTF-8을 이해합니다!
Doctor Coder

2
좋아요, 약 10 초 후에 전체 게시물을 읽었습니다. "Python은 인코딩과 관련하여 짜증납니다."
Andrew

좋은 설명입니다. 질문 을 해결할 수 있습니까?
Maggyero

26

유니 코드 문자가 표준 출력으로 인쇄되면 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를 사용하여 유니 코드를 터미널에 씁니다. 글꼴이 지원하는 경우 UnicodeEncodeError 경고가없고 올바른 문자가 표시됩니다. 글꼴 지원 하지 않더라도 터미널에서 지원 글꼴이있는 응용 프로그램으로 문자를 잘라 붙여 넣을 수 있으며 올바른 글꼴입니다. 업그레이드!


8

Python REPL은 환경에서 사용할 인코딩을 선택합니다. 그것이 제정신의 것을 발견하면 그것은 모두 그냥 작동합니다. 그것은 무슨 일이 일어나고 있는지 파악할 수 없을 때입니다.

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

3
호기심에서 sys.stdout.encoding을 ASCII로 어떻게 변경합니까?
Michael Ekoka

2
@TankorSmash 나는 TypeError: readonly attribute2.7.2에 가고 있어요
코스

4

당신은 명시 적으로 유니 코드 문자열을 입력하여 인코딩을 지정했습니다. u접두사를 사용하지 않은 결과를 비교하십시오 .

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

>>> 

의 경우 \xe9다음 파이썬 따라서 인쇄 ... 뭔가 빈을 기본 인코딩 (ASCII)를 가정합니다.


1
내가 유니 코드 문자열 (코드 포인트)를 인쇄 할 때 내가 잘 이해한다면, 파이썬 내가 대신 그냥 걸 주려고의 UTF-8로 인코딩 출력을 원하는 것으로 가정 할 수 아스키에 있었다?
Michael Ekoka

1
@ mike : AFAIK 당신이 말한 것이 맞습니다. 이 경우 유니 코드 문자를 인쇄하지만, ASCII로 인코딩, 모든 깨져 나올 것이다 아마 모든 초보자가 요구 될 것이다 "어떻게 유니 코드 텍스트를 인쇄 할 수 없습니다 오지?"
Mark Rushakoff

2
감사합니다. 나는 실제로 그 초보자 중 하나이지만 유니 코드에 대해 약간의 이해가있는 사람들의 측면에서 왔기 때문에이 행동으로 인해 나를 조금 버렸습니다.
Michael Ekoka

3
'\ xe9'가 ASCII 문자 세트에 없으므로 R.이 올바르지 않습니다. 비 유니 코드 문자열은 sys.stdout.encoding을 사용하여 인쇄되고, 유니 코드 문자열은 인쇄하기 전에 sys.stdout.encoding으로 인코딩됩니다.
Mark Tolonen

0

그것은 나를 위해 작동합니다 :

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

1
필연적으로 다른 무언가를 깰 저렴한 더러운 해킹. 올바른 방법으로하는 것이 어렵지 않습니다!
Chris Johnson

0

당으로 파이썬 기본 / 암시 적 문자열 인코딩 및 변환 :

  • printing unicode일 때는 encoded입니다 <file>.encoding.
    • 이 때 encoding설정되지는이 unicode암시 적으로 변환됩니다 str(그것을위한 코덱이기 때문에 sys.getdefaultencoding(), 즉 ascii, 어떤 국가의 문자가이 발생할 것입니다 UnicodeEncodeError)
    • 표준 스트림의 경우 encoding환경에서 추론됩니다. 일반적으로 tty(터미널의 로케일 설정에서) fot 스트림을 설정하지만 파이프에는 설정되지 않을 가능성이 있습니다
      • 따라서 print u'\xe9'출력이 터미널에있을 때 성공하고 리디렉션되면 실패합니다. 해결책은 ing encode()전에 원하는 인코딩으로 문자열에 대한 print것입니다.
  • print보내고 str한, 바이트 스트림으로 전송됩니다. 터미널이 표시하는 글리프는 로케일 설정에 따라 다릅니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.