파일로 리디렉션 할 때 UnicodeDecodeError


100

나는 우분투 터미널에서 한 번에, (UTF-8로 설정을 인코딩), 두 번이 코드를 실행 ./test.py하고 다음과 ./test.py >out.txt:

uni = u"\u001A\u0BC3\u1451\U0001D10C"
print uni

리디렉션하지 않으면 쓰레기를 인쇄합니다. 리디렉션을 사용하면 UnicodeDecodeError가 발생합니다. 누군가 두 번째 경우에만 오류가 발생하는 이유를 설명하거나 두 경우 모두 커튼 뒤에서 일어나는 일에 대해 자세히 설명 할 수 있습니까?


답변도 도움이 될 수 있습니다.
tzot 2011 년

찾은 결과를 복제하려고하면 UnicodeDecodeError가 아닌 UnicodeEncodeError가 발생합니다. gist.github.com/jaraco/12abfc05872c65a4f3f6cd58b6f9be4d
제이슨 R. 쿰즈

답변:


252

이러한 인코딩 문제에 대한 모든 열쇠는 원칙적으로이 있다는 것을 이해하는 것입니다 "문자열"의 두 가지 개념 (1)의 문자열 : 문자 의, (2) 문자열 / 배열 바이트. 이 구분은 256 자 이하의 인코딩 (ASCII, Latin-1, Windows-1252, Mac OS Roman 등)의 역사적 편재성 때문에 오랫동안 대부분 무시되었습니다. 이러한 인코딩은 공통 문자 집합을 다음에 매핑합니다. 0에서 255 사이의 숫자 (예 : 바이트); 웹이 등장하기 전에 상대적으로 제한된 파일 교환으로 인해 호환되지 않는 인코딩 상황이 허용됩니다. 대부분의 프로그램은 동일한 운영 체제에 남아있는 텍스트를 생성하는 한 여러 인코딩이 있다는 사실을 무시할 수 있습니다. 이러한 프로그램은 단순히 운영 체제에서 사용하는 인코딩을 통해 텍스트를 바이트로 처리합니다. 정확하고 현대적인보기는 다음 두 가지 사항을 기반으로이 두 문자열 개념을 적절하게 분리합니다.

  1. 문자 는 대부분 컴퓨터와 관련이 없습니다 . 예를 들어 بايثون, 中 蟒 및 🐍과 같이 초크 보드 등에 그릴 수 있습니다. 기계의 "문자"에는 공백, 캐리지 리턴, 쓰기 방향 설정 지침 (아랍어 등), 악센트 등과 같은 "그리기 지침"도 포함됩니다. 유니 코드 표준 에는 매우 큰 문자 목록 이 포함되어 있습니다. 알려진 문자의 대부분을 다룹니다.

  2. 반면에 컴퓨터는 어떤 방식 으로든 추상 문자를 표현해야합니다.이를 위해 메모리가 바이트 청크로 제공되기 때문에 바이트 배열 (0에서 255 사이의 숫자 포함)을 사용합니다. 문자를 바이트로 변환하는 데 필요한 프로세스를 인코딩 이라고 합니다. 따라서 컴퓨터 는 문자를 표현하기 위해 인코딩이 필요 합니다. 컴퓨터에있는 모든 텍스트는 표시 될 때까지 인코딩되거나 (특정 방식으로 인코딩 된 문자를 예상하는) 터미널로 전송되거나 파일에 저장됩니다. 표시되거나 적절하게 "이해"(예 : Python 인터프리터)하기 위해 바이트 스트림이 문자로 디코딩 됩니다. 몇 가지 인코딩(UTF-8, UTF-16,…)은 문자 목록에 대해 유니 코드에 의해 정의됩니다. 따라서 유니 코드는 문자 목록과 이러한 문자에 대한 인코딩을 모두 정의합니다. "유니 코드 인코딩"이라는 표현을 유비쿼터스 UTF-8을 참조하는 방법이지만 유니 코드는 여러 인코딩을 제공하므로 잘못된 용어 입니다.

요약하면 컴퓨터는 내부적으로 bytes 를 사용하여 문자를 나타내야 하며 다음 두 가지 작업을 통해 수행합니다.

인코딩 : 문자 → 바이트

디코딩 : 바이트 → 문자

일부 인코딩은 모든 문자 (예 : ASCII)를 인코딩 할 수 없지만 (일부) 유니 코드 인코딩을 사용하면 모든 유니 코드 문자를 인코딩 할 수 있습니다. 일부 문자는 직접 또는 조합 (예 : 기본 문자 및 악센트) 으로 표현 될 수 있기 때문에 인코딩이 반드시 고유 한 것은 아닙니다 .

개행 이라는 개념은 운영 체제에 따라 다른 (제어) 문자로 표현 될 수 있기 때문에 복잡한 계층을 추가 합니다 (이것이 Python의 범용 개행 파일 읽기 모드 의 이유입니다 ).

이제 위에서 "문자"라고 부르는 것은 유니 코드가 " 사용자 인식 문자 " 라고 부르는 것 입니다. 사용자가 인식하는 단일 문자는 " 코드 포인트 " 라고하는 유니 코드 목록의 서로 다른 색인 에있는 문자 부분 (기본 문자, 악센트 등) 을 결합하여 유니 코드로 표현할 수 있습니다. 이러한 코드 포인트를 함께 결합하여 형성 할 수 있습니다. "문자 소 클러스터". 따라서 유니 코드는 바이트와 문자열 사이에 있고 후자에 더 가까운 유니 코드 코드 포인트 시퀀스로 구성된 세 번째 문자열 개념으로 이어집니다. 나는 그것들을 " 유니 코드 문자열 " 이라고 부를 것이다 (파이썬 2 에서처럼).

Python은 (사용자가 인식하는) 문자의 문자열을 인쇄 할 수 있지만 Python이 아닌 바이트 문자열은 기본적 으로 사용자가 인식하는 문자가 아니라 유니 코드 코드 포인트의 시퀀스입니다 . 코드 포인트 값은 Python \u\U유니 코드 문자열 구문 에서 사용되는 값 입니다. 문자 인코딩과 혼동해서는 안됩니다 (그리고 그와 어떤 관계도 가질 필요가 없습니다 : 유니 코드 코드 포인트는 다양한 방법으로 인코딩 될 수 있습니다).

이것은 중요한 결과를 가져 옵니다 : Python (유니 코드) 문자열의 길이는 코드 포인트의 수이며, 항상 사용자가 인식하는 문자의 수 아닙니다 . 따라서 s = "\u1100\u1161\u11a8"; print(s, "len", len(s))(Python 3)은 단일 사용자가 인식 (한국어) 각 len 3했음에도 불구하고 제공합니다. s(그렇게 할 필요가 없더라도 3 개의 코드 포인트로 표현되기 때문입니다 print("\uac01").) 그러나 많은 실제 상황에서 많은 문자가 일반적으로 Python에서 단일 유니 코드 코드 포인트로 저장되기 때문에 문자열의 길이는 사용자가 인식하는 문자의 수입니다.

에서는 파이썬 2 유니 코드 문자열 "유니 코드 문자열 '(...라고 unicode형 리터럴 형태 u"…"바이트 배열은"문자열 "(반면) str바이트 어레이는 예를 들어 문자열 상수로 구성 될 수있는 형식 "…"). 에서 파이썬 3 , 유니 코드 문자열은 단순히 "문자열"(라고 str유형, 문자 양식을 "…"바이트 배열은 "바이트"(반면) bytes유형, 문자 그대로의 형태 b"…"). 결과적으로 다음과 같은 "🐍"[0]결과가 Python 2 ( '\xf0', 한 바이트) 및 Python 3 ( "🐍", 첫 번째이자 유일한 문자) 에서 다른 결과를 제공합니다 .

이 몇 가지 핵심 사항을 통해 대부분의 인코딩 관련 질문을 이해할 수 있습니다!


일반적으로 터미널에 인쇄 u"…" 때 쓰레기가 발생해서는 안됩니다. Python은 터미널의 인코딩을 알고 있습니다. 실제로 터미널에서 예상하는 인코딩을 확인할 수 있습니다.

% python
Python 2.7.6 (default, Nov 15 2013, 15:20:37) 
[GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.2.79)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> print sys.stdout.encoding
UTF-8

입력 문자를 터미널의 인코딩으로 인코딩 할 수있는 경우 Python은이를 수행하고 불평없이 해당 바이트를 터미널로 보냅니다. 그러면 터미널은 입력 바이트를 디코딩 한 후 문자를 표시하기 위해 최선을 다할 것입니다 (최악의 경우 터미널 글꼴에는 일부 문자가없고 대신 어떤 종류의 공백이 인쇄됩니다).

입력 문자를 터미널의 인코딩으로 인코딩 할 수없는 경우 터미널이 이러한 문자를 표시하도록 구성되지 않았 음을 의미합니다. 파이썬은 불평 할 것입니다 (파이썬에서는 UnicodeEncodeError문자열이 터미널에 맞는 방식으로 인코딩 될 수 없기 때문에). 가능한 유일한 해결책은 문자를 표시 할 수있는 터미널을 사용하는 것입니다 (문자를 나타낼 수있는 인코딩을 허용하도록 터미널을 구성하거나 다른 터미널 프로그램을 사용하여). 이것은 다른 환경에서 사용할 수있는 프로그램을 배포 할 때 중요합니다. 인쇄하는 메시지는 사용자의 터미널에서 표현할 수 있어야합니다. 따라서 때때로 ASCII 문자 만 포함하는 문자열을 사용하는 것이 가장 좋습니다.

그러나 프로그램 의 출력리디렉션하거나 파이프 할 때 일반적으로 수신 프로그램의 입력 인코딩이 무엇인지 알 수 없으며 위 코드는 None (Python 2.7) 또는 UTF-8 ( 파이썬 3) :

% python2.7 -c "import sys; print sys.stdout.encoding" | cat
None
% python3.4 -c "import sys; print(sys.stdout.encoding)" | cat
UTF-8

그러나 stdin, stdout 및 stderr의 인코딩은 필요한 경우 환경 변수를 통해 설정할 수 있습니다 PYTHONIOENCODING.

% PYTHONIOENCODING=UTF-8 python2.7 -c "import sys; print sys.stdout.encoding" | cat
UTF-8

터미널에 인쇄해도 예상 한 결과가 나오지 않으면 수동으로 입력 한 UTF-8 인코딩이 올바른지 확인할 수 있습니다. 예를 들어, 내가 착각\u001A 하지 않았다면 첫 번째 문자 ( )는 인쇄 할 수 없습니다 .

에서 http://wiki.python.org/moin/PrintFails , 파이썬 2.x를위한 다음과 같은 솔루션을 찾을 수 있습니다 :

import codecs
import locale
import sys

# Wrap sys.stdout into a StreamWriter to allow writing unicode.
sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout) 

uni = u"\u001A\u0BC3\u1451\U0001D10C"
print uni

Python 3 의 경우 이전 에 StackOverflow 에서 질문 한 질문 중 하나를 확인할 수 있습니다 .


2
@singularity : 감사합니다! Python 3에 대한 정보를 추가했습니다.
Eric O Lebigot

2
고마워요! 오랜만에이 설명이 필요했습니다 ... 하나의 찬성표 만 줄 수있어서 유감입니다.
mik01aj

3
@ m01 님, 도움이되어 기쁩니다! 이 답변을 작성하게 된 동기 중 하나는 웹에 유니 코드와 Python에 대한 페이지가 많았 기 때문입니다.하지만 흥미롭지 만 구체적인 인코딩 문제를 완전히 해결할 수는 없다는 것을 알게되었습니다. 이 답변에서 찾은 원칙 구체적인 인코딩 문제를 해결할 때 시간내어 사용하면 많은 도움이됩니다.
에릭 O Lebigot

3
이것은 유니 코드와 파이썬에 대한 최고의 설명입니다. 파이썬 유니 코드 하우투는 이것으로 대체되어야합니다.
stantonk

1
여기, 나 ...이 칠판에 "오른쪽에서 왼쪽으로 재정의"문자를 그려 보자
icktoofay

20

파이썬은 터미널, 파일, 파이프 등에 쓸 때 항상 유니 코드 문자열을 인코딩합니다. 터미널에 쓸 때 파이썬은 일반적으로 터미널의 인코딩을 결정하고 올바르게 사용할 수 있습니다. 파일 또는 파이프에 쓸 때 Python은 명시 적으로 달리 언급하지 않는 한 'ascii'인코딩을 기본값으로 사용합니다. Python은 PYTHONIOENCODING환경 변수를 통해 출력을 파이핑 할 때 수행 할 작업을 지시 할 수 있습니다 . 쉘은 Python 출력을 파일 또는 파이프로 리디렉션하기 전에이 변수를 설정하여 올바른 인코딩을 알 수 있습니다.

귀하의 경우에는 터미널이 글꼴에서 지원하지 않는 4 개의 드문 문자를 인쇄했습니다. 다음은 내 터미널에서 실제로 지원되는 문자 (UTF-8이 아닌 cp437 사용)를 사용하여 동작을 설명하는 데 도움이되는 몇 가지 예입니다.

예 1

점을 유의 #coding주석이있는 인코딩을 나타내는 소스 파일이 저장됩니다. 터미널에서 할 수없는 소스의 문자를 지원할 수 있도록 utf8을 선택했습니다. 인코딩이 stderr로 리디렉션되어 파일로 리디렉션 될 때 볼 수 있습니다.

#coding: utf8
import sys
uni = u'αßΓπΣσµτΦΘΩδ∞φ'
print >>sys.stderr,sys.stdout.encoding
print uni

출력 (터미널에서 직접 실행)

cp437
αßΓπΣσµτΦΘΩδ∞φ

파이썬은 터미널의 인코딩을 올바르게 결정했습니다.

출력 (파일로 리디렉션 됨)

None
Traceback (most recent call last):
  File "C:\ex.py", line 5, in <module>
    print uni
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-13: ordinal not in range(128)

파이썬은 인코딩 (없음)을 결정할 수 없으므로 'ascii'기본값을 사용했습니다. ASCII는 유니 코드의 처음 128 자 변환 만 지원합니다.

출력 (파일로 리디렉션 됨, PYTHONIOENCODING = cp437)

cp437

내 출력 파일이 정확했습니다.

C:\>type out.txt
αßΓπΣσµτΦΘΩδ∞φ

예 2

이제 터미널에서 지원하지 않는 문자를 소스에 넣겠습니다.

#coding: utf8
import sys
uni = u'αßΓπΣσµτΦΘΩδ∞φ马' # added Chinese character at end.
print >>sys.stderr,sys.stdout.encoding
print uni

출력 (터미널에서 직접 실행)

cp437
Traceback (most recent call last):
  File "C:\ex.py", line 5, in <module>
    print uni
  File "C:\Python26\lib\encodings\cp437.py", line 12, in encode
    return codecs.charmap_encode(input,errors,encoding_map)
UnicodeEncodeError: 'charmap' codec can't encode character u'\u9a6c' in position 14: character maps to <undefined>

내 단말기는 마지막 한자를 인식하지 못했습니다.

출력 (직접 실행, PYTHONIOENCODING = 437 : replace)

cp437
αßΓπΣσµτΦΘΩδ∞φ?

인코딩으로 오류 처리기를 지정할 수 있습니다. 이 경우 알 수없는 문자가 ?. ignore그리고 xmlcharrefreplace몇 가지 다른 옵션입니다. UTF8 (모든 유니 코드 문자 인코딩을 지원함)을 사용하는 경우 대체가 수행되지 않지만 문자를 표시하는 데 사용되는 글꼴 은 여전히이를 지원해야합니다.


"파일이나 파이프에 쓸 때 파이썬은 명시 적으로 달리 언급하지 않는 한 기본적으로 'ascii'인코딩을 사용합니다."라는 것은 정확히 사실이 아닙니다. 실제로 Python 3는 Mac OS X / Fink에서 UTF-8을 사용합니다.
Eric O Lebigot 2011 년

2
예, Python 3의 기본값은 'utf8'이지만 OP의 샘플을 기반으로 그는 Python 2.X를 사용하고 있으며 기본값은 'ascii'입니다.
Mark Tolonen 2011 년

조작하여 올바른 출력을 얻을 수 없습니다 PYTHONIOENCODING. 이렇게 print string.encode("UTF-8")@Ismail이 나를 위해 일에 의해 제안.
tripleee

chcp코드 페이지에서 지원하지 않더라도 글꼴에서 지원하면 한자를 볼 수 있습니다. 를 피하기 위해 패키지를 UnicodeEncodeError: 'charmap'설치할 수 win-unicode-console있습니다.
jfs

내 문제는 python-gitlab CLI가 cmd에서 중국어 문자를 잘 인쇄하지만 파일로 리디렉션 된 후 문자가 쓰레기라는 것입니다. PYTHONIOENCODING=utf-8문제를 해결합니다.
ElpieKay

12

인쇄하는 동안 인코딩

uni = u"\u001A\u0BC3\u1451\U0001D10C"
print uni.encode("utf-8")

이는 스크립트를 수동으로 실행할 때 python이이를 터미널로 출력하기 전에 인코딩하고, 파이프 할 때 python이 자체적으로 인코딩하지 않으므로 I / O를 수행 할 때 수동으로 인코딩해야하기 때문입니다.


4
WTH가 여기서 진행되고있는 질문에 여전히 답이 없습니다. 왜 갑자기 프로세스에 완전히 투명해야 할 때 리디렉션 될 때만 인코딩하기로 결정합니다.
Maxim Sloyko 2010

리디렉션을 수행 할 때 Python이 인코딩하지 않는 이유는 무엇입니까? 파이썬은 어렵게 일을 다르게 할 것이라고 명시 적으로 확인하고 결정합니까?
Arafangion 2010

1
파이썬은 두 가지 상황을 구별하는 방법을 가지고 있습니까? 나는 (지금까지 ...) 그것이 알 수있는 방법이 없다는 것을 알고있다.
zedoo

4
파이썬은 출력이 터미널인지, 파이프로 출력한다면 터미널 유형이 "벙어리"인지 확인할 수 있습니다. 나는 "멍청한"이이 경우에 파이썬이 자동으로 어떤 일도하지 않는 이유를 알려 주어야한다고 생각한다. 실패 할 수있다.
ismail 2010-12-28

1
환경이 utf-8과 호환되지 않는 문자 인코딩을 사용하는 경우 mojibake를 생성합니다 (예 : Windows에서 일반적 임). 스크립트 내에서 환경의 문자 인코딩을 하드 코딩하지 마십시오. 로케일 또는 PYTHONIOENCODING을 구성하거나 설치 win-unicode-console(Windows)하거나 명령 행 매개 변수를 승인하십시오 (필요한 경우).
jfs
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.