web-dev-qa-db-ja.com

デフォルトのエンコードがASCIIのときにPythonがUnicode文字を出力するのはなぜですか?

Python 2.6シェルから:

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

「é」文字はASCIIの一部ではなく、エンコードを指定していないため、printステートメントの後に、意味のないまたはエラーが発生することが予想されました。 ASCIIがデフォルトのエンコードの意味を理解していないと思います。

編集

編集をAnswersセクションに移動し、提案どおりに受け入れました。

136
Michael Ekoka

さまざまな返信からの断片のおかげで、説明をつなげることができると思います。

Unicode文字列u '\ xe9'を印刷しようとすると、Pythonは、現在sys.stdout.encodingに格納されているエンコードスキームを使用して、その文字列を暗黙的にエンコードしようとします。 Pythonは、実際に開始された環境からこの設定を選択します。環境から適切なエンコードが見つからない場合にのみ、そのdefaultASCIIに戻ります。

たとえば、エンコードがデフォルトの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文字を出力しようとすると、Niceエラーメッセージが表示されるはずです。

>>> 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で出力をデコードするようにターミナルを設定します(グラフィックターミナルには通常ドロップダウンメニューのいずれかで文字エンコーディングを設定します。これは実際のShell環境のエンコーディングを変更せず、terminalそれ自体は、指定された出力をデコードします。これは、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文字列をimplicitlyエンコードしようとします。インスタンスは「UTF-8」です。 UTF-8エンコード後、結果のバイナリ文字列は '\ xc3\xa9'です(後の説明を参照)。端末はストリームをそのまま受信し、latin-1を使用して0xc3a9をデコードしようとしますが、latin-1は0から255になるため、一度に1バイトだけストリームをデコードします。 0xc3a9は2バイト長なので、latin-1デコーダーはそれを0xc3(195)および0xa9(169)として解釈し、Ãと©の2文字を生成します。

(3)pythonは、latin-1スキームでUnicodeコードポイントu '\ xe9'(233)をエンコードします。 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はbinary文字列をそのまま出力します。端末は、そのストリームをUTF-8でデコードしようとします。しかし、UTF-8は値0xe9(後の説明を参照)を理解しないため、Unicodeコードポイントに変換できません。コードポイントが見つからず、文字が印刷されません。

(5)pythonは、sys.stdout.encodingにあるものでUnicode文字列を暗黙的にエンコードしようとします。まだ「UTF-8」。結果のバイナリ文字列は「\ xc3\xa9」です。端末はストリームを受信し、UTF-8を使用して0xc3a9をデコードしようとします。バックコード値0xe9(233)を生成します。これは、Unicode文字マップで記号「é」を指します。端末に「é」が表示されます。

(6)pythonはlatin-1でUnicode文字列をエンコードし、同じ値 '\ xe9'のバイナリ文字列を生成します。繰り返しますが、端末の場合、これはケース(4)とほとんど同じです。

結論:-Pythonは、デフォルトのエンコードを考慮せずに、非Unicode文字列を生データとして出力します。現在のエンコーディングがデータと一致する場合、端末は偶然それらを表示します。 -Pythonは、sys.stdout.encodingで指定されたスキームを使用してエンコードしたUnicode文字列を出力します。 -Pythonは、シェルの環境からその設定を取得します。 -端末は、独自のエンコード設定に従って出力を表示します。 -端末のエンコーディングは、シェルのエンコーディングとは無関係です。


nicode、UTF-8、およびlatin-1の詳細:

ユニコードは基本的に、いくつかのキー(コードポイント)がいくつかのシンボルを指すように割り当てられている文字のテーブルです。例えば慣例により、キー0xe9(233)が記号「é」を指す値であることが決定されています。 ASCIIとUnicodeは、0〜127の同じコードポイントを使用します。latin-1と0〜255のUnicodeは同じです。つまり、0x41はASCII、latin-1とUnicode、0xc8の 'A'を指しますlatin-1およびUnicodeの 'Ü'を指し、0xe9はlatin-1およびUnicodeの 'é'を指します。

電子デバイスを使用する場合、Unicodeコードポイントは電子的に表現する効率的な方法を必要とします。それがエンコーディングスキームの目的です。さまざまなUnicodeエンコード方式が存在します(utf7、UTF-8、UTF-16、UTF-32)。最も直感的で簡単なエンコーディングアプローチは、Unicodeマップのコードポイントの値を電子形式の値として使用することですが、Unicodeには現在、100万を超えるコードポイントがあります。表現された。テキストを効率的に処理するには、実際の必要性に関係なく、すべてのコードポイントを文字ごとに最小3バイトで正確に同じ量のスペースに格納する必要があるため、1対1のマッピングはかなり非実用的です。

ほとんどのエンコーディングスキームにはスペース要件に関する欠点があり、最も経済的なものはすべてのユニコードコードポイントをカバーするわけではありません。たとえば、asciiは最初の128のみをカバーし、latin-1は最初の256をカバーします。一般的な「安い」文字であっても、必要以上のバイトを必要とするため、無駄です。たとえば、UTF-16では、アスキー範囲(65である「B」、依然としてUTF-16で2バイトのストレージが必要)内の文字を含む、文字ごとに最小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」は、2バイトでエンコードされたコードポイントの開始をUTF-8デコーダーに示しますが、「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つ)を使用して文字列をデコードするように設定されている場合、éが表示されます。latin-1の0xc3がÃを指し、0xa9が©を指すためです。

101
Michael Ekoka

Unicode文字が標準出力に出力されるとき、sys.stdout.encodingが使用されます。非Unicode文字は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に別のオプションがない場合にのみ使用されます。

Python 3.6以降では、Windowsのエンコードが無視され、Unicode APIを使用してUnicodeが端末に書き込まれることに注意してください。 UnicodeEncodeErrorの警告はなく、フォントがサポートしている場合は正しい文字が表示されます。フォントdoes n'tがサポートされている場合でも、サポートされているフォントを使用して、端末からアプリケーションに文字をカットアンドペーストできます。アップグレード!

25
Mark Tolonen

Python REPLは、環境から使用するエンコードを選択しようとします。それが正気な何かを見つけた場合、それはすべてちょうど動作します。バグが発生するのは、何が起こっているのかわからないときです。

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

have明示的なUnicode文字列を入力してエンコードを指定しました。 uプレフィックスを使用しない結果を比較します。

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

>>> 

\xe9の場合、Pythonはデフォルトのエンコーディング(Ascii)を想定しているため、...何か空白を印刷します。

4
Mark Rushakoff

わたしにはできる:

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

Pythonのデフォルト/暗黙的な文字列のエンコードと変換 に従って:

  • printing unicodeの場合、<file>.encoding。を含むencodedです。
    • encodingが設定されていない場合、unicodeは暗黙的にstrに変換されます(そのコーデックはsys.getdefaultencoding()、つまりasciiであるため、国別文字はUnicodeEncodeErrorを引き起こす
    • 標準ストリームの場合、encodingは環境から推測されます。通常はfot ttyストリーム(端末のロケール設定から)が設定されますが、パイプには設定されない可能性があります
      • そのため、print u'\xe9'は、出力が端末への場合は成功し、リダイレクトされた場合は失敗します。解決策は、printingの前にencode()希望するエンコーディングの文字列を使用することです。
  • printing strの場合、バイトはそのままストリームに送信されます。端末が表示するグリフは、ロケール設定によって異なります。
0
ivan_pozdeev