[解決済み] なぜPythonはデフォルトのエンコーディングがASCIIなのにunicodeの文字を表示するのですか?
質問
Python 2.6シェルからです。
>>> import sys
>>> print sys.getdefaultencoding()
ascii
>>> print u'\xe9'
é
>>>
私は、"é" という文字はASCIIの一部ではないし、エンコーディングを指定していないので、print文の後に何かちんぷんかんぷんなものかErrorが発生すると思っていたのです。ASCIIがデフォルトのエンコーディングであることの意味を理解していないのでしょう。
EDIT
に編集を移しました。 回答 セクションに移動し、提案されたとおりに受け入れました。
どのように解決するのですか?
さまざまな回答からの断片のおかげで、私たちは説明を縫うことができると思います。
u'\xe9' という Unicode 文字列を出力しようとすると、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が文字列を出力した後に何が起こるかを観察してみましょう。このために、まずグラフィックターミナル (私は Gnome Terminal を使っています) 内で bash シェルを起動し、出力を ISO-8859-1 または latin-1 でデコードするようにターミナルを設定します (グラフィックターミナルには、通常 文字エンコードを設定する というオプションがあります)。これは実際の シェル環境の のエンコーディングは変更されません。 ターミナル が与えられた出力をデコードする方法を変えるだけで、 ウェブブラウザが行うのと同じようなものです。したがって、シェルの環境とは無関係にターミナルのエンコーディングを変更することができます。では、シェルから 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 は 暗黙のうちに Unicode文字列をsys.stdout.encodingで現在設定されているスキーム、この例では"UTF-8"でエンコードします。UTF-8でエンコードした結果、バイナリ文字列は「' \xc3㎟'」となります(後の説明参照)。ターミナルはこのストリームを受け取って、latin-1を使って0xc3a9をデコードしようとしますが、latin-1は0から255まであるので、ストリームを1バイトずつしかデコードできないのです。0xc3a9は2バイトなので、latin-1デコーダはこれを0xc3 (195) と0xa9 (169) と解釈し、2文字を生成する。Ãと©の2文字が生成されます。
(3) pythonはunicodeのコードポイントu' \xe9' (233)をlatin-1スキームでエンコードしています。latin-1コードポイントの範囲は0-255で、その範囲内のUnicodeと全く同じ文字を指していることがわかりました。したがって、その範囲内のユニコードコードポイントは、latin-1方式でエンコードされたとき、同じ値になります。つまり、u' \xe9' (233) を latin-1 でエンコードすると、バイナリ文字列 '\xe9' も生成されます。Terminalはその値を受け取り、latin-1文字マップ上でマッチングを試みます。(1)と同様に、"é"となり、これが表示されます。
ここで、ターミナルのエンコーディング設定をドロップダウンメニューからUTF-8に変更してみましょう(Webブラウザのエンコーディング設定を変更するのと同じです)。Python を停止したり、シェルを再起動したりする必要はありません。ターミナルのエンコーディングはPythonのものと一致するようになりました。もう一度印刷を試してみましょう。
>>> print '\xe9' # (4)
>>> print u'\xe9' # (5)
é
>>> print u'\xe9'.encode('latin-1') # (6)
>>>
(4) pythonの出力は バイナリ の文字列をそのまま出力します。TerminalはそのストリームをUTF-8でデコードしようとします。しかし、UTF-8 は値 0xe9 を理解しないため(後の説明を参照)、ユニコード コード ポイントに変換することができません。コードポイントが見つからず、文字も出力されません。
(5) pythonが試したのは 暗黙のうちに でエンコードしようとします。まだ "UTF-8"です。結果として得られるバイナリ文字列は '\xc3
(6) python が Unicode 文字列を latin-1 でエンコードすると、同じ値 '\xe9' を持つバイナリ文字列が生成されます。これも端末では(4)の場合とほぼ同じです。
結論 - Pythonはデフォルトのエンコーディングを考慮することなく、非ユニコード文字列を生データとして出力します。ターミナルは、現在のエンコーディングがデータに一致する場合、それらを表示するだけです。 - Python は sys.stdout.encoding で指定されたスキームを使用してエンコードした後に Unicode 文字列を出力します。 - Pythonはシェルの環境からその設定を取得します。 - ターミナルはそれ自身のエンコーディングの設定に従って出力を表示します。 - ターミナルのエンコーディングはシェルのエンコーディングとは独立しています。
unicode、UTF-8、latin-1についての詳細です。
Unicode は基本的に文字の表で、いくつかのキー (コード ポイント) は慣習的にいくつかの記号を指すように割り当てられています。たとえば、慣習的にキー 0xe9 (233) は記号 'é' を指す値であると決められています。ASCIIとUnicodeは0から127まで、latin-1とUnicodeは0から255まで、同じコードポイントを使用します。つまり、0x41 は ASCII、latin-1、Unicode で 'A' を指し、0xc8 は latin-1 と Unicode で 'Ü' を指し、0xe9 は latin-1 と Unicode で 'é' を指します。
電子デバイスで作業するとき、Unicode コードポイントは電子的に表現される効率的な方法を必要とします。それが、エンコーディングのスキームです。様々な Unicode 符号化スキームが存在します(utf7、UTF-8、UTF-16、UTF-32)。最も直感的でわかりやすい符号化方式は、Unicodeマップにあるコードポイントの値をそのまま電子的な値として使うことですが、現在Unicodeには100万以上のコードポイントがあり、中には3バイトで表現しなければならないコードポイントもあることになります。テキストで効率的に作業するには、1 対 1 のマッピングはかなり非現実的です。なぜなら、実際の必要性に関係なく、1 文字あたり最低 3 バイトの、まったく同じ量のスペースにすべてのコード ポイントを格納しなければならないからです。
ほとんどのエンコード方式は、必要なスペースに関して欠点があり、最も経済的なものは、すべての Unicode コードポイントをカバーしていません。例えば、ascii は最初の 128 をカバーするだけですが、latin-1 は最初の 256 をカバーします。また、より包括的であろうとするものは、一般的なquot;cheap"キャラクタであっても必要以上のバイトを必要とするため、結局は無駄なものとなってしまうのです。たとえばUTF-16は、Asciiの範囲にある文字も含めて、1文字あたり最低2バイトを使います(65文字の「B」は、UTF-16でも2バイトのストレージを必要とします)。UTF-32 はさらに無駄が多く、すべての文字を 4 バイトで保存します。
UTF-8 は偶然にもこのジレンマを巧みに解決し、可変量のバイトスペースでコードポイントを格納できるスキームを備えています。そのエンコーディング戦略の一部として、UTF-8 はコードポイントにフラグビットを混入し、(おそらくデコーダーに)そのスペース要件とその境界を示すようにしました。
ascii 範囲 (0-127) の unicode コード ポイントの UTF-8 エンコーディング。
0xxx xxxx (in binary)
- x は、エンコード中にコードポイントを格納するために予約された実際のスペースを示します。
- 先頭の 0 は、UTF-8 デコーダに対して、このコードポイントが 1 バイトしか必要としないことを示すフラグです。
- をエンコードする際、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 コードポイント (non-ascii) の UTF-8 エンコーディング。
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' フラグビットは、内部バイトの開始を知らせるために使用されます。
- は、エンコード後に 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エンコード後の0xe9ユニコードコードポイントは0xc3a9になります。これは、まさに端末がそれを受け取る方法です。もしあなたの端末が latin-1 (非 Unicode レガシー エンコードの 1 つ) を使用して文字列をデコードするように設定されている場合、é と表示されますが、これは latin-1 の 0xc3 が Ã、0xa9 が © を指すという偶然によります。
関連
-
[解決済み】Python regex AttributeError: 'NoneType' オブジェクトに 'group' 属性がない。
-
[解決済み] builtins.TypeError: strでなければならない、bytesではない
-
[解決済み] print関数の出力をフラッシュする(pythonの出力をバッファリング解除する)にはどうすればよいですか?
-
[解決済み] 特定のUnicode文字を含むコメントでのJavaコードの実行が許可されているのはなぜですか?
-
[解決済み] Pythonの@propertyデコレーターはどのように機能するのでしょうか?
-
[解決済み] なぜPythonのコードは関数の中でより速く実行されるのですか?
-
[解決済み] Pythonのunicode文字列のアクセントを除去(正規化)する最良の方法は何ですか?
-
[解決済み] 最近のPerlはなぜデフォルトでUTF-8を避けるのですか?
-
[解決済み] ASCIIとUnicodeの違いは何ですか?
-
[解決済み] Pythonでstdoutをパイピングするときに正しいエンコードを設定する
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
PicgoのイメージベッドツールをPythonで実装する
-
Python カメの描画コマンドとその例
-
pythonサイクルタスクスケジューリングツール スケジュール詳解
-
Pythonコードの可読性を向上させるツール「pycodestyle」の使い方を詳しく解説します
-
PythonでECDSAを実装する方法 知っていますか?
-
[解決済み】OSError: [WinError 193] %1 は有効な Win32 アプリケーションではありません。
-
[解決済み】 AttributeError: モジュール 'matplotlib' には属性 'plot' がない。
-
[解決済み】TypeErrorを取得しました。エントリを持つ子テーブルの後に親テーブルを追加しようとすると、 __init__() missing 1 required positional argument: 'on_delete'
-
[解決済み】 AttributeError("'str' object has no attribute 'read'")
-
[解決済み】ValueError: xとyは同じサイズでなければならない