Webエンジニアのメモ帳

技術的な話を中心に書いています。

【Python】文字コードについて

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でエンコードしています。

Pythonunicodeでデータを持っていますが、これを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でも解釈できるバイト列なので文字で表示される

ということです。