Edited at

Python3でファイルを扱う時の文字エンコーディング

More than 1 year has passed since last update.


概要


  • 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 では openencoding引数を受け取れるようになったのでそれを利用すれば良い(=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


参考