LoginSignup
23
17

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-07-01

概要

  • 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

参考

23
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
17