デフォルトのエンコーディングがASCIIの場合、なぜPythonはUnicode文字を出力するのですか?


139

Python 2.6シェルから:

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

「é」文字はASCIIの一部ではなく、エンコーディングも指定していないため、printステートメントの後に意味不明またはエラーが発生するはずです。デフォルトのエンコーディングであるASCIIが何を意味するのか理解できません。

編集

編集を[ 回答]セクションに移動し、提案どおりに承認しました。


6
その編集を代わりに回答に変えて、それを受け入れることができれば、それはかなり素晴らしいでしょう。
メルカトル

2
印刷'\xe9'用に構成され、端末にUTF-8にはなりません印刷しますé\xe9有効なUTF-8シーケンスではないため、置換文字(通常は疑問符)が出力されます(先頭のバイトの後に続くはずの2バイトが欠落しています)。代わりに、Latin-1として解釈されることはほとんどありませ
Martijn Pieters

2
@MartijnPieters出力\xe9にprint を出力するときに、ターミナルがISO-8859-1(latin1)でデコードするように設定されていることを指定した部分にざっと目を通した可能性がありますé
Michael Ekoka 14

2
ああ、その部分を逃した。端末はシェルとは異なる構成になっています 小切手。
Martijn Pieters

私は答えをざっと読みましたが、実際には、Python 2.7のuプレフィックスのない文字列があります。なぜそれがまだユニコードとして扱われるのですか?(my sys.getdefaultencoding()is ascii)
dtc

回答:


104

さまざまな返信からの断片のおかげで、私たちは説明をつなぎ合わせることができると思います。

ユニコード文字列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つ)を使用して文字列をデコードするように設定されている場合は、éが表示されます。


6
素晴らしい説明。UTF-8が理解できました!
Doctor Coder

2
わかりました。投稿全体を約10秒で読み終えました。「Pythonはエンコーディングに関しては最悪だ」と語った。
Andrew

素晴らしい説明。この質問に答えてもらえますか?
マジェロ2018

26

Unicode文字がstdoutに出力されるときに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警告は表示されず、フォントがサポートしている場合は正しい文字が表示されます。フォントフォントをサポートしていない場合でも、サポートされているフォントを使用て、端末からアプリケーションに文字を切り取って貼り付けることができ、正しく表示されます。アップグレード!


8

Python REPLは、使用するエンコーディングを環境から取得しようとします。それは何か正気を見つけた場合、それはすべてうまくいきます。バグが発生しているのは、何が起こっているのかわからないときです。

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

3
好奇心から、sys.stdout.encodingをasciiに変更するにはどうすればよいですか?
Michael Ekoka

2
@TankorSmash私はTypeError: readonly attribute2.7.2に乗っています
Kos

4

あなたはしている明示的にUnicode文字列を入力することにより、エンコーディングを指定しました。u接頭辞を使用しない場合の結果を比較します。

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

>>> 

その場合、\xe9Pythonはデフォルトのエンコーディング(Ascii)を想定しているため、...空白を出力します。


1
よく理解すれば、Unicode文字列(コードポイント)を出力するとき、Pythonは、ASCII で可能だったものを単に与えるのではなく、utf-8でエンコードされた出力が必要だと想定しますか?
Michael Ekoka

1
@mike:あなたの言ったことは正しいです。それは場合はやった Unicode文字をプリントアウトが、ASCIIとしてエンコードされ、すべてが文字化け出てくるだろうし、おそらくすべての初心者は、求められる「私はUnicodeテキストをプリントアウトすることはできませんどのように来ます?」
Mark Rushakoff、2010

2
ありがとうございました。私は実際にはそれらの初心者の1人ですが、Unicodeをある程度理解している人の側から来ているので、この動作が私を少し落胆させています。
Michael Ekoka

3
R.、不正解です。 '\ xe9'はASCII文字セットに含まれていません。非Unicode文字列はsys.stdout.encodingを使用して印刷され、Unicode文字列は印刷前にsys.stdout.encodingにエンコードされます。
Mark Tolonen、2010

0

わたしにはできる:

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

1
他のものを必然的に破壊する安価な汚いハック。正しい方法で行うことは難しくありません!
クリスジョンソン

0

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

  • printingのときunicodeはでencoded <file>.encodingです。
    • encodingが設定されていない場合、はunicode暗黙的にに変換されますstr(そのためのコーデックはsys.getdefaultencoding()、つまりascii、国別文字が原因でUnicodeEncodeError
    • 標準ストリームの場合、これencodingは環境から推測されます。これは通常、tty(端末のロケール設定から)fot ストリームを設定しますが、パイプには設定されない可能性があります
      • したがって、print u'\xe9'出力が端末への場合は成功し、リダイレクトされた場合は失敗する可能性があります。解決策はencode()printing する前に、目的のエンコードを使用して文字列を処理することです。
  • ときprintINGのstrであるとして、バイトがストリームに送信されます。端末が表示するグリフは、そのロケール設定によって異なります。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.