Python2からPython3への書き換えをした際に、文字コードに関する部分でかなり詰まったので、色々と調べました。
また文字コード関係はPython2とPython3でかなり違いますが、この記事ではPython3に関する内容について説明しています。
そもそも文字コードについて
そもそも文字というのは内部的には0と1の羅列です。
例えばutf_8という文字コードの場合は、「11100011100000011000001」は「あ」という文字として解釈されるわけです。
文字コードには色々な種類があります。
上記の「11100011100000011000001」をutf_8で読めば「あ」になりますが、別の文字コードで読むと全く違う文字列になったり、読めなかったりするわけです。
Pythonでファイルを読み込む
Pythonはファイルを読み込むと、その内容をunicodeで保持します。ファイルを読み込む際の文字コードについては後述します。
unicodeで「あ」がどのように表現されているか見てみます。
>>> ord("あ") 12354 >>> bin(12354) '0b11000001000010'
このように、「あ」は「11000001000010」になっています。 つまり、Pythonは0と1で表されたファイルの中身を、そのファイルとはまた別の0と1の羅列で扱うのです。
encode() と decode()
繰り返しになりますがPythonは文字列をunicodeで扱っています。 encode()はunicodeから別の文字コードに変換する関数で、decode()はその逆です。
以下の例をみてください。
>>> "あ".encode("utf-8") b'\xe3\x81\x82'
この例では、「あ」をutf_8でエンコードしています。
Pythonはunicodeでデータを持っていますが、これをutf_8に、つまり 「11000001000010」を「11100011100000011000001」に変換しているわけです。
ですから出力されるのは「11100011100000011000001」になりますが、これはunicodeでは解釈できないバイト列です。
そのため、バイト列がそのまま、16進数に変換されて出力されているのです。
LANGについて
ではファイルを読み書きする際に選ばれる文字コードは、どのように設定されるのでしょうか。
実は、Python3ではLANGという環境変数から取得しています。
LANGの値を確認するにはターミナルで以下のコマンドを実行します。
$ echo $LANG ja_JP.UTF-8
ちなみに冒頭で「詰まった」と書いていた件ですが、このLANGが設定されていないため、asciiでファイルを読もうとして、文字コードエラー、という現象が起こっていました。
LANGの値が設定されていない場合でも、codecsというモジュールを使うことで解決することができますが、ここでは割愛します。
Pythonのスクリプトの先頭の #coding: utf_8について
入門書通りに何も考えずに書いていましたが、これはPythonのスクリプトを正しく読み込ませるためのものです。
この一文がないとスクリプトがutf_8で書かれていても、asciiという文字コードで解釈されます。
ちなみに、日本語(や他の言語)が書かれていない場合は、書かなくても構いません。
スクリプトファイルがutf_8で書かれていてもです。
なぜかというと、数字やアルファベットはasciiでもunicodeでもutf_8でも、全て同じバイト列で表現されるからです。
>>> "a".encode("utf_8") b'a' >>> "a".encode("ascii") b'a'
先ほどはencode()を実行した際に16進数が出力されましたが、今度は普通に'a'が出力されています。これは、encode()によって0と1の羅列であるバイト列に変換されても、そのバイト列がunicodeで解釈できるものだったからです。
つまり、
- 日本語だと、バイト列をutf_8でエンコード → 別のバイト列(0と1の羅列)になる → unicodeで解釈できないので16進数で表示される
- 英語だと、バイト列をutf_8でエンコード → 同じバイト列(0と1の羅列)になる → unicodeでも解釈できるバイト列なので文字で表示される
ということです。