さまざまな返信からの断片のおかげで、私たちは説明をつなぎ合わせることができると思います。
ユニコード文字列u '\ xe9'を出力しようとすると、Pythonは現在sys.stdout.encodingに格納されているエンコードスキームを使用して、その文字列を暗黙的にエンコードしようとします。Pythonは実際には、この設定を、それが開始された環境から取得します。環境から適切なエンコーディングが見つからない場合にのみ、デフォルトの ASCIIに戻します。
たとえば、エンコーディングがデフォルトでUTF-8になっているbashシェルを使用します。私がそれからPythonを開始すると、それはピックアップしてその設定を使用します:
$ python
>>> import sys
>>> print sys.stdout.encoding
UTF-8
少しの間、Pythonシェルを終了して、bashの環境を偽のエンコーディングで設定してみましょう。
$ export LC_CTYPE=klingon
# we should get some error message here, just ignore it.
次に、Pythonシェルを再度起動し、実際にデフォルトのASCIIエンコーディングに戻されることを確認します。
$ python
>>> import sys
>>> print sys.stdout.encoding
ANSI_X3.4-1968
ビンゴ!
ASCIIの外でUnicode文字を出力しようとすると、素敵なエラーメッセージが表示されます。
>>> print u'\xe9'
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9'
in position 0: ordinal not in range(128)
Pythonを終了して、bashシェルを破棄します。
Pythonが文字列を出力した後に何が起こるかを観察します。このため、最初にグラフィックターミナル内でbashシェルを起動し(私はGnomeターミナルを使用します)、ターミナルをISO-8859-1(別名latin-1)で出力をデコードするように設定します(グラフィックターミナルには通常、文字を設定するオプションがありますドロップダウンメニューのいずれかでエンコードします)。これは実際のシェル環境のエンコーディングを変更しないことに注意してください。これは、ターミナル自体が指定された出力をデコードする方法を変更するだけで、Webブラウザーとは少し異なります。したがって、シェルの環境から独立して、端末のエンコーディングを変更できます。次に、シェルから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)pythonはバイナリ文字列をそのまま出力し、ターミナルはそれを受信し、その値をlatin-1文字マップと一致させようとします。latin-1では、0xe9または233は文字「é」を生成するため、ターミナルに表示されます。
(2)python は、現在sys.stdout.encodingに設定されているスキームを使用してUnicode文字列を暗黙的にエンコードしようとします。この例では、「UTF-8」です。UTF-8エンコード後、結果のバイナリ文字列は '\ xc3 \ xa9'になります(後の説明を参照)。端末はそのようにストリームを受信し、latin-1を使用して0xc3a9をデコードしようとしますが、latin-1は0から255になるため、一度に1バイトのみストリームをデコードします。0xc3a9は2バイト長なので、latin-1デコーダーはそれを0xc3(195)と0xa9(169)として解釈し、2文字(yieldと©)を生成します。
(3)Pythonは、Unicodeコードポイントu '\ xe9'(233)をlatin-1スキームでエンコードします。latin-1コードポイントの範囲は0〜255であり、その範囲内のUnicodeとまったく同じ文字を指していることがわかります。したがって、その範囲のUnicodeコードポイントは、latin-1でエンコードされた場合と同じ値になります。そのため、latin-1でエンコードされたu '\ xe9'(233)もバイナリ文字列 '\ xe9'を生成します。端末はその値を受け取り、latin-1文字マップ上でそれを照合しようとします。ケース(1)と同様に、「é」が生成され、それが表示されます。
次に、ターミナルのエンコード設定をドロップダウンメニューからUTF-8に変更します(Webブラウザーのエンコード設定を変更するのと同じです)。Pythonを停止したり、シェルを再起動したりする必要はありません。端末のエンコーディングがPythonのエンコーディングと一致するようになりました。もう一度印刷してみましょう。
>>> print '\xe9' # (4)
>>> print u'\xe9' # (5)
é
>>> print u'\xe9'.encode('latin-1') # (6)
>>>
(4)pythonはバイナリ文字列をそのまま出力します。端末はそのストリームをUTF-8でデコードしようとします。しかし、UTF-8は値0xe9を理解しないため(後述の説明を参照)、それをUnicodeコードポイントに変換できません。コードポイントが見つかりません。文字は印刷されません。
(5)Pythonは、sys.stdout.encodingにあるものでUnicode文字列を暗黙的にエンコードしようとします。それでも「UTF-8」。結果のバイナリ文字列は '\ xc3 \ xa9'です。端末はストリームを受信し、UTF-8を使用して0xc3a9のデコードを試みます。これは、バックコード値0xe9(233)を生成します。これは、Unicode文字マップで記号「é」を指します。端末には「é」が表示されます。
(6)PythonはUnicode文字列をlatin-1でエンコードし、同じ値 '\ xe9'のバイナリ文字列を生成します。繰り返しになりますが、ターミナルの場合、これはケース(4)とほとんど同じです。
結論:-Pythonは、デフォルトのエンコーディングを考慮せずに、非Unicode文字列を生データとして出力します。現在のエンコーディングがデータと一致する場合、端末はたまたまそれらを表示します。-Pythonは、sys.stdout.encodingで指定されたスキームを使用してエンコードした後、Unicode文字列を出力します。-Pythonはその設定をシェルの環境から取得します。-端末は、独自のエンコーディング設定に従って出力を表示します。-端末のエンコーディングはシェルのエンコーディングから独立しています。
ユニコード、UTF-8、ラテン-1の詳細:
Unicodeは基本的に、いくつかの記号を指すようにいくつかのキー(コードポイント)が従来割り当てられている文字のテーブルです。たとえば、慣例により、キー0xe9(233)は記号「é」を指す値であると決定されています。ASCIIおよびUnicodeは、latin-1およびUnicodeが0〜255と同様に、0〜127の同じコードポイントを使用します。つまり、0x41はASCIIの「A」を指し、latin-1およびUnicode、0xc8はlatin-1およびUnicode、0xe9はlatin-1およびUnicodeの「é」を指します。
電子デバイスで作業する場合、Unicodeコードポイントは、電子的に表現する効率的な方法を必要とします。それがエンコーディングスキームの目的です。さまざまなUnicodeエンコード方式が存在します(utf7、UTF-8、UTF-16、UTF-32)。最も直感的で簡単なエンコーディングアプローチは、Unicodeマップのコードポイントの値を電子フォームの値として使用することですが、Unicodeは現在100万を超えるコードポイントを持っているため、一部には3バイトが必要です。表現した。テキストを効率的に処理するには、実際のニーズに関係なく、すべてのコードポイントを1文字あたり最低3バイトで、まったく同じ量のスペースに格納する必要があるため、1対1のマッピングは実用的ではありません。
ほとんどのエンコーディングスキームにはスペース要件に関する欠点があり、最も経済的なものはすべてのUnicodeコードポイントをカバーしていません。たとえば、ASCIIは最初の128しかカバーしていませんが、Latin-1は最初の256をカバーしています。一般的な「安価な」文字であっても、必要以上のバイトを必要とするため、無駄になります。たとえば、UTF-16は、ASCII範囲の文字を含め、1文字あたり最低2バイトを使用します( 'B'は65ですが、UTF-16では2バイトのストレージが必要です)。UTF-32はすべての文字を4バイトで格納するため、さらに無駄が多くなります。
UTF-8は、ジレンマを巧みに解決しており、可変長のバイトスペースでコードポイントを格納できる方式を採用しています。エンコード戦略の一部として、UTF-8は、(おそらくデコーダーに)スペース要件と境界を示すフラグビットでコードポイントを結び付けます。
ASCII範囲(0〜127)のUnicodeコードポイントのUTF-8エンコード:
0xxx xxxx (in binary)
- xは、エンコード中にコードポイントを「保存」するために予約された実際のスペースを示します
- 先頭の0は、このコードポイントが1バイトのみを必要とすることをUTF-8デコーダーに示すフラグです。
- エンコード時に、UTF-8はその特定の範囲内のコードポイントの値を変更しません(つまり、UTF-8でエンコードされた65も65です)。UnicodeとASCIIも同じ範囲で互換性があることを考えると、偶然にもUTF-8とASCIIもその範囲で互換性があります。
たとえば、「B」のUnicodeコードポイントは、バイナリでは「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を超えるUnicodeコードポイントの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は、エンコード後にUnicodeコードポイント値が格納されるスペースを示します。
たとえば、 'é' Unicodeコードポイントは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エンコーディングが0xc3a9になった後の0xe9 Unicodeコードポイント。これはまさに端末がそれを受け取る方法です。ターミナルがlatin-1(非Unicodeレガシーエンコーディングの1つ)を使用して文字列をデコードするように設定されている場合は、éが表示されます。