ファイルにリダイレクトするときのUnicodeDecodeError


100

私はUbuntuの端末に一回で、(UTF-8に設定をコードする)、二回このスニペットを実行./test.pyし、その後で./test.py >out.txt

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

リダイレクトしないと、ゴミが表示されます。リダイレクトすると、UnicodeDecodeErrorが発生します。誰かが2番目のケースでのみエラーが発生する理由を説明できますか、それとも両方のケースでカーテンの後ろで何が起こっているのかを詳しく説明できますか?


この回答も役に立ちます。
tzot

あなたの発見を再現しようとすると、UnicodeDecodeErrorではなく、UnicodeEncodeErrorを受け取ります。gist.github.com/jaraco/12abfc05872c65a4f3f6cd58b6f9be4d
Jason R. Coombs

回答:


252

このようなエンコーディングの問題の鍵は、原則として 「string」は2つの異なる概念の(1)文字列:文字、及び(2)ストリング/アレイバイト。256文字以下のエンコーディング(ASCII、Latin-1、Windows-1252、Mac OS Romanなど)の歴史的な偏在のため、この区別はほとんど長い間無視されてきました。これらのエンコーディングは、一連の一般的な文字を0〜255(バイト)の数値。ほとんどのプログラムは、同じオペレーティングシステムに残っているテキストを生成する限り、複数のエンコーディングが存在するという事実を無視できるため、Webの登場前の比較的限られたファイル交換で、この互換性のないエンコーディングの状況が許容されました。テキストをバイトとして扱います(オペレーティングシステムで使用されるエンコーディングを使用)。正しい最新のビューでは、次の2つのポイントに基づいて、これら2つの文字列の概念を適切に分離しています。

  1. キャラクターはほとんどコンピューターとは無関係です。例えばチョップボードなどにそれらを描くことができます。たとえば、بايثون、中華やandなどです。マシンの「文字」には、スペース、キャリッジリターン、書き込み方向を設定する指示(アラビア語など)、アクセントなどの「描画指示」も含まれます。非常に大きな文字リストUnicode標準に含まれています。既知の文字のほとんどをカバーしています。

  2. 一方、コンピュータは何らかの方法で抽象文字を表す必要があります。そのため、メモリはバイトチャンクで提供されるため、バイトの配列(0〜255の数値を含む)を使用します。文字をバイトに変換するために必要なプロセスは、エンコーディングと呼ばれます。したがって、コンピュータが必要とする文字を表すためにエンコーディングをします。コンピューターに存在するテキストは、(それが表示されるまで)エンコードされます。ターミナルに送信されるか(特定の方法でエンコードされた文字が必要です)、ファイルに保存されます。(たとえば、Pythonインタプリタによって)表示されるか、適切に「理解」されるために、バイトのストリームは文字にデコードされます。いくつかのエンコーディング(UTF-8、UTF-16、…)は、文字のリストに対してUnicodeによって定義されています(したがって、Unicodeは、文字のリストとこれらの文字のエンコーディングの両方を定義します。「Unicodeエンコーディング」という表現が、ユビキタスUTF-8を参照する方法ですが、Unicodeは複数のエンコーディングを提供するため、これは誤った用語です。

要約すると、コンピューターはバイト文字を内部的に表現する必要があり、次の2つの操作で表現します

エンコーディング:文字→バイト

デコード:バイト→文字

一部のエンコーディングではすべての文字(ASCIIなど)をエンコードできませんが、(一部の)UnicodeエンコーディングではすべてのUnicode文字をエンコードできます。一部の文字は直接または組み合わせて(たとえば、基本文字とアクセントの)表すことができるため、エンコーディングは必ずしも一意である必要はありません

改行 の概念は、オペレーティングシステムに依存するさまざまな(制御)文字で表すことができるため、複雑なレイヤーを追加することに注意してください(これがPythonのユニバーサル改行ファイル読み取りモードの理由です)。

ここで、上記で「文字」と呼んでいるのは、Unicodeが「ユーザー認識文字」と呼んでいるものです。単一のユーザー認識文字は、「コードポイント」と呼ばれる、Unicodeリストの異なるインデックスにある文字部分(基本文字、アクセントなど)を組み合わせることにより、Unicodeで表すことができます。これらのコードポイントを組み合わせて、フォームを作成できます。 「書記素クラスター」。したがって、Unicodeは、バイト文字列と文字列の間に位置し、後者に近い、一連のUnicodeコードポイントで構成される文字列の3番目の概念につながります。それらを " Unicode文字列と呼びます Python 2のように」」。

Python は(ユーザーが認識する)文字の文字列を出力できますがPythonの非バイト文字列は基本的に、ユーザーが認識する文字ではなく、Unicodeコードポイントのシーケンスです。コードポイント値は、Python \uおよび\UUnicode文字列構文で使用される値です。文字のエンコードと混同しないでください(文字との関係を負う必要はありません。Unicodeコードポイントはさまざまな方法でエンコードできます)。

これには重要な結果があります。Python(Unicode)文字列の長さは、コードポイントの数であり、必ずしもユーザーが認識する文字の数とは限りません。したがって、s = "\u1100\u1161\u11a8"; print(s, "len", len(s))(Python 3)は、単一のユーザーが認識する(韓国語)각 len 3にもかかわらずs文字(3つのコードポイントで表されるため、必要がない場合でも、print("\uac01")です。ただし、多くの文字は通常Pythonによって単一のUnicodeコードポイントとして格納されるため、多くの実際的な状況では、文字列の長さはユーザーが認識する文字の数になります。

Pythonの2、Unicode文字列は、「Unicode文字列」(...と呼ばれるunicodeタイプ、リテラル形式u"…"バイト配列は「文字列」(であるが、)strバイトの配列は、例えば文字列リテラルで構成することができるタイプ"…")。でPythonの3、Unicode文字列を単に「文字列」(と呼ばれる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はそれを行い、対応するバイトを文句なしに端末に送信します。端末は、入力バイトをデコードした後に文字を表示するために最善を尽くします(最悪の場合、端末のフォントには一部の文字がなく、代わりにある種の空白が印刷されます)。

入力文字を端末のエンコーディングでエンコードできない場合、端末がこれらの文字を表示するように構成されていないことを意味します。Pythonは文句を言います(PythonではUnicodeEncodeError、端末に適した方法で文字列をエンコードできないため、aを使用します)。考えられる唯一の解決策は、文字を表示できる端末を使用することです(文字を表現できるエンコードを受け入れるように端末を構成するか、別の端末プログラムを使用します)。これは、さまざまな環境で使用できるプログラムを配布するときに重要です。印刷するメッセージは、ユーザーの端末で表現できる必要があります。したがって、ASCII文字のみを含む文字列を使用するのが最善の場合もあります。

ただし、プログラムの出力リダイレクトまたはパイプする場合、受信プログラムの入力エンコーディングが何であるかを知ることは通常不可能であり、上記のコードはいくつかのデフォルトのエンコーディングを返します:None(Python 2.7)またはUTF-8( Python 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は、Python 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で以前尋ねられた質問の1つを確認できます。


2
@特異性:ありがとう!私は、Python 3用にいくつかの情報を追加
エリック・O Lebigot

2
ありがとう、男!長い間この説明が必要でした...賛成票を1つだけしか与えられないのは残念です。
mik01aj

3
@ m01、助けてくれて嬉しいです!この回答を書く動機の1つは、ウェブ上にUnicodeとPythonに関する多くのページがあったことですが、興味深いにもかかわらず、具体的なエンコードの問題を完全に解決することはできなかったことがわかりました。この回答にある原則と、具体的なエンコードの問題を解決するときに時間をかけてそれらを使用すると、非常に役立ちます。
Eric O Lebigot

3
これは、これまでのunicodeとpythonの最も良い説明です。Python Unicode HOWTOはこれに置き換える必要があります。
スタントン、2013年

1
ここで、この黒板に「右から左への上書き」文字を描画させてみましょう…
icktoofay 2013

20

ターミナル、ファイル、パイプなどに書き込む場合、Pythonは常にUnicode文字列をエンコードします。ターミナルに書き込む場合、Pythonは通常、ターミナルのエンコードを決定して正しく使用できます。ファイルまたはパイプに書き込むとき、明示的に指示されない限り、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
αßΓπΣσµτΦΘΩδ∞φ

Pythonは端末のエンコーディングを正しく決定しました。

出力(ファイルにリダイレクト)

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)

Pythonはエンコーディング(None)を決定できなかったため、「ascii」のデフォルトを使用しました。ASCIIは、Unicodeの最初の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
αßΓπΣσµτΦΘΩδ∞φ?

エラーハンドラはエンコーディングで指定できます。この場合、不明な文字はに置き換えられました?ignorexmlcharrefreplace他のいくつかのオプションです。UTF8(すべてのUnicode文字のエンコードをサポートする)を使用する場合、置換は行われませんが、文字の表示に使用されるフォントは引き続きそれらをサポートする必要があります。


「ファイルまたはパイプに書き込むとき、明示的に指示されない限り、Pythonのデフォルトは「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年

操作しても正しい出力が得られませんでしたPYTHONIOENCODINGprint 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でエンコードされ、パイプでパイプすると、それ自体はエンコードされないため、I / Oを実行するときに手動でエンコードする必要があるためです。


4
それでも、WTHがここで起こっている質問には答えません。なぜ、それはプロセスに対して完全に透過的であると想定されているときに、リダイレクトされたときにのみエンコードすることを決定します。
Maxim Sloyko、2010

リダイレクトを実行するときに、Pythonがそれをエンコードしないのはなぜですか?Pythonは明示的にチェックし、難しいために別の方法で行うことを決定しますか?
アラファンギオン2010

1
Pythonは2つの状況を区別する方法さえ持っていますか?私は(今までは...)それを知る方法がないと思っています。
zedoo 2010

4
Pythonは、出力がターミナルかどうかを確認できます。パイプに出力する場合、ターミナルのタイプは「ダム」になります。この場合、「ダム」がPythonが自動的に何も実行しない理由を教えてくれるはずです。失敗する可能性があります。
ismail 2010

1
環境がutf-8と互換性のない文字エンコーディングを使用している場合、mojibakeを生成します(たとえば、Windowsでは一般的です)。スクリプト内に環境の文字エンコードをハードコーディングしないでください。ロケール、PYTHONIOENCODING、またはインストールwin-unicode-console(Windows)を構成するか、(必要に応じて)コマンドラインパラメータを受け入れます。
jfs 2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.