1. ホーム
  2. python

[解決済み] ファイルへのリダイレクト時にUnicodeDecodeErrorが発生する。

2022-12-20 01:01:27

質問

このスニペットを2回、Ubuntuのターミナル(エンコーディングはutf-8に設定)で、1回は ./test.py で、次に ./test.py >out.txt :

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

リダイレクトなしだとゴミが表示される。リダイレクトを行うと、UnicodeDecodeErrorが発生します。なぜ2番目のケースでだけエラーが発生するのか、あるいはさらに、両方のケースでカーテンの後ろで何が起こっているのかの詳細な説明を誰か教えてください。

どのように解決するのですか。

このようなエンコーディングの問題に対する全体の鍵は、原理的に string"の2つの異なる概念があることを理解することです。 : (1) 文字列の 文字 と、(2)文字列/配列の バイト . この区別は、256 文字以下のエンコーディング (ASCII、Latin-1、Windows-1252、Mac OS Roman など) が歴史的に広く使われていたため、長い間ほとんど無視されてきました:これらのエンコーディングは、一連の共通文字を 0 ~ 255 の数字にマッピングします (つまり ウェブが登場する以前は、ファイルの交換が比較的限られていたため、互換性のないエンコーディングという状況は許容されていました。ほとんどのプログラムは、同じオペレーティングシステム上でテキストを作成する限り、複数のエンコーディングが存在するという事実を無視できたからです。正しい現代の見解は、次の 2 つのポイントに基づいて、これら 2 つの文字列の概念を適切に分離するものです。

  1. 文字 は、ほとんどが コンピュータに関係ない 機械用の文字には、例えばスペース、キャリッジリターン、(アラビア語などの)書く方向を設定する命令、アクセントなどのような、「描画命令」も含まれます。A 非常に大きな文字リスト に含まれています。 ユニコード 規格に含まれており、既知の文字のほとんどをカバーしています。

  2. 一方、コンピュータは抽象的な文字を何らかの方法で表現する必要があります。このため、コンピュータは バイトの配列 (0から255までの数字が含まれます)を使用します。なぜなら、コンピュータのメモリはバイト単位だからです。文字をバイトに変換するのに必要な処理は エンコーディング . したがって、コンピュータは が必要とするのは エンコーディングが必要なのです。 端末に送信する場合も(端末は特定の方法でエンコードされた文字を期待します)、ファイルに保存する場合も、コンピュータ上に存在するテキストはすべて(表示されるまで)エンコードされています。 表示したり、(例えばPythonインタプリタが)正しく理解したりするために、バイトのストリームは次のようになります。 デコード 文字に変換されます。 いくつかのエンコーディング (UTF-8, UTF-16,...) は、Unicode がその文字のリストに対して定義しています (Unicode はこのように、文字のリストとこれらの文字のエンコーディングの両方を定義しています。ユビキタスな UTF-8 を参照する方法として "Unicode encoding" という表現を目にする場所がまだありますが、これは間違った用語法であり、Unicode では 複数の エンコーディング) を提供しているからです。

まとめると コンピュータは内部的に文字をバイトで表現する必要がある で表現する必要があり、2 つの操作によってそれを実現しています。

エンコード : 文字 → バイト

デコード : バイト → 文字

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

の概念に注意してください。 改行 は複雑なレイヤーを追加します。 は、オペレーティングシステムに依存した異なる(制御)文字で表現されることがあるため(これはPythonの 汎用改行ファイル読み込みモード ).


Unicode、文字、およびコードポイントに関するいくつかの詳細について、興味があればご覧ください。

さて、私が上で "character" と呼んだものは、Unicode が " と呼ぶものです。 ユーザーが認識する文字 と呼んでいます。ユーザが認識する1つの文字は、ユニコードでは、異なる場所にある文字部分(基本文字、アクセント、...)を組み合わせることによって表現されることがあります。 インデックス と呼ばれる、ユニコード一覧の異なるインデックスにある文字部分(基本文字、アクセント...)を組み合わせることによって、ユニコードで表現できることがあります。 コードポイント これらのコードポイントを組み合わせて、quot;grapheme cluster" を形成することができます。 このように、Unicodeは、バイト文字列と文字列の間に位置し、後者に近い、Unicodeコードポイントのシーケンスで構成される第3の文字列の概念を導きだします。私はこれを「"」と呼ぶことにします。 ユニコード文字列 と呼ぶことにします(Python 2のように)。

Pythonは 印刷 という(ユーザが認識する)文字列を出力します。 Python の非バイト文字列は、基本的に Unicode コードポイントのシーケンスです。 であり、ユーザが認識する文字ではありません。コードポイントの値は、Pythonの \u\U Unicode 文字列構文です。 これらは、文字のエンコーディングと混同してはいけません(また、文字と何らかの関係を持つ必要もありません。Unicodeのコードポイントは様々な方法でエンコードすることができます)。

これは重要な結果をもたらします。 は、Python (Unicode) 文字列の長さはそのコードポイントの数であり、これは ではなく 常にユーザーが認識する文字数です。 このように s = "\u1100\u1161\u11a8"; print(s, "len", len(s)) (Python 3) は 각 len 3 ところが s は、ユーザーが認識する(韓国語の)文字が1つであること(3つのコードポイントで表現されるため-たとえその必要がなくても、たとえば print("\uac01") が示すように)。しかし、多くの実用的な状況では、文字列の長さはユーザーが認識する文字の数です。なぜなら、多くの文字は通常 1 つの Unicode コード ポイントとして Python によって格納されるからです。

Python 2 で、Unicode文字列は... "Unicode文字列"と呼ばれています( unicode 型、リテラル形式 u"…" ) であるのに対し、バイト配列は "文字列" ( str 型であり、バイト配列は例えば文字列リテラルを用いて構築できます。 "…" ). で Python 3 では、Unicode文字列は単に"string"と呼ばれます( str 型、リテラル形式 "…" ) であり、バイト配列は "bytes" ( bytes 型,リテラル形式 b"…" ). その結果、以下のような "????"[0] のようなものは、Python 2 では異なる結果をもたらします ( '\xf0' バイト)とPython3( "????" という、最初で唯一の文字)。

これらのいくつかのキーポイントで、ほとんどのエンコーディング関連の質問を理解することができるはずです!


通常、あなたが を印刷します。 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 という文字列が端末に合った方法でエンコードされていないため)。唯一の可能な解決策は、文字を表示できる端末を使うことです(あなたの文字を表すことができるエンコーディングを受け入れるように端末を設定するか、別の端末プログラムを使うか)。これは、異なる環境で使用されるプログラムを配布する場合に重要なことで、印刷するメッセージはユーザーの端末で表現可能であるべきです。このように、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 で見ることができます。