概要
- Python3 では
open
などでファイルを扱う時のデフォルトの文字エンコーディングはOSに依存する - Unix(Linux)系では、
locale(LC_CTYPE)
に依存する。 - 何も考えずにファイルの読み書きをすると、環境によっては
UnicodeDecodeError
などに遭遇する
確認
- 手元の macOS で動きを確認する
- 例えば、日本語が書かれた utf-8 のテキストファイルがあるとする。このファイルを開いて中身を取得する
with open('utf-8.txt', mode='r') as fp:
text = fp.read()
- 特にエラーもなく開いてファイルの中身を取得できる
- これは macOS が 文字エンコーディングがデフォルト UTF-8 だから
- 実際に利用される文字エンコーディングは
locale.getpreferredencoding
で確認できる
>> import locale
>> locale.getpreferredencoding()
UTF-8
- getpreferredencoding が
UTF-8
なので、utf-8のテキストがエラーもなく読める。 - 実際に
LC_CTYPE
を変えてエラーになるのを確認する - 一時的に
LC_CTYPE
を変える時はsetlocale
を使う
import locale
locale.setlocale(locale.LC_CTYPE, ('C'))
print(locale.getpreferredencoding(False)) # => US-ASCII になる
with open('hoge.txt') as fp:
text = fp.read()
結果
US-ASCII
Traceback (most recent call last):
File "test.py", line 7, in <module>
text = fp.read()
File "/path/to/encodings/ascii.py", line 26, in decode
return codecs.ascii_decode(input, self.errors)[0]
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe3 in position 0: ordinal not in range(128)
- LC_CTYPE を C に設定したことで、文字エンコーディング US-ASCII になる
- その結果 uff-8 のテキストを read する時に
UnicodeDecodeError
となった
メモ
- setloacaleを使わずに LC_CTYPE の環境変数を直接変更しても同じ挙動を確認できる
- getpreferredencoding(do_setloacal=False) にしないと、setlocaleで一時的に変更したエンコーディングが取得できなかった。
対応
- 基本的には、ファイルを扱う時には、文字エンコーディングを指定するのが良い
- Python3 では
open
がencoding
引数を受け取れるようになったのでそれを利用すれば良い(=LC_CTYPE
には関係なくファイルを扱える)
with open('utf-8.txt', encoding='utf-8') as fp:
text = fp.read()
- python2, python3 の両方で動くようなライブラリを書くのであれば、binaryモードで開いてからutf-8にするか、codecsモジュールを使うのが良い。
#! -*- coding:utf-8 -*-
import locale
import codecs
import six
locale.setlocale(locale.LC_CTYPE, ('C'))
with open('utf-8.txt', 'rb') as fp:
text1 = fp.read()
text1 = six.text_type(text1, 'utf-8')
with codecs.open('utf-8.txt', 'r', encoding='utf-8') as fp:
text2 = fp.read()
assert text1 == text2
まとめ
- Python3 は OS 及び
locale(LC_CTYPE)
に依存してファイルを扱う時のデフォルトの文字エンコーディング決定する - 基本的に文字エンコーディングを指定した上でファイルを扱うのが良い。でないと意図せぬ不具合に遭遇する。
- 最近そういうやらかしをしまして, すいませんという話です
- https://github.com/zengin-code/zengin-py/pull/4