Python 2系で UTF-8 を取り扱う際の心得
Python 2系での日本語の扱い方について、忘れた頃に忘れてしまうのでメモ。
ソースで日本語(UTF-8)を使う
まずは基本から。
ソースの1行目または2行目にコメントを入れ、正規表現 coding[:=]\s*([-\w.]+) にマッチする文字列を書けばよい。
普通は以下のように書く。
# -*- coding: utf-8 -*-
shebang 付き (ここで Python のバージョンを指定したほうがよい場合もある)
#!/usr/bin/env python # -*- coding: utf-8 -*-
3つの文字列クラス
Python 2系の場合、今扱っているオブジェクトが何者かを意識することが大切。
- str
ASCII文字列向けに設計された文字列クラスだが、その中にはテキスト以外も格納できるので
実態はバイト配列のようなものとなっている。
>>> 'abc' 'abc' >>> '\x03' '\x03' >>> 'あいう' '\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86' >>> type('abc') <type 'str'>
- unicode
あらゆる文字コードのUnicode文字列を抽象化したクラス。
>>> u'abc' u'abc' >>> u'\x03' u'\x03' >>> u'あいう' u'\u3042\u3044\u3046' >>> print u'\u3042\u3044\u3046' あいう >>> type(u'abc') <type 'unicode'>
- basestring
基底の抽象クラス。
文字列クラスかどうかを判定する場合に使用。
>>> isinstance('abc', str) True >>> isinstance(u'abc', str) False >>> isinstance('abc', basestring) True >>> isinstance(u'abc', basestring) True
2つのメソッド
str 型から unicode 型への変換をデコード、その逆をエンコードと呼ぶ。
その変換処理は str クラスのメソッドとして定義されている。
- str.encode() = str(unicode_value) : unicode => str
5. Built-in Types — Python v2.7.5 documentation - str.decode() = unicode(str_value) : str => unicode
5. Built-in Types — Python v2.7.5 documentation
これらのメソッドは、encoding と errors という 2つの引数を取る。
- encoding: 変換に使用する codec 名。
デフォルトは defaultencoding の値。
defaultencoding は sys.getdefaultencoding() で取得できるもので、そのデフォルトは 'ascii'。 - erros: エラーハンドラ名。デフォルトは 'strict' (変換に失敗したら例外を発生)
明示的にエンコードする
厄介なエンコードエラーが発生する主な要因は、暗黙的なエンコード・デコード処理と
エンコーディングのミスマッチにある。
>>> str(u'あいう') Traceback (most recent call last): File "<stdin>", line 1, in <module> UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-2: ordinal not in range(128)
これは前述の str.encode() メソッドにおける encoding が省略された状態で実行されたのと同じ現象が起きている。
この場合は utf-8 でデコードされた unicode オブジェクト(u'あいう')が、
defaultencoding である ascii でエンコードされたためにエラーとなってしまったのだ。
とはいえ実行時に defaultencoding を変更するのは、不可能ではないが特別な理由が無い限りすべきではない。
他のモジュールへもその影響が波及してしまうため、思わぬトラブルを引き起こすリスクがある。
解決策の一つは、以下のように明示的にエンコード・デコードを行うこと。そうすればエラーは起こらない。
>>> str(u'あいう'.encode('utf-8')) '\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86'
"Zen of Python" の『曖昧さにあたっては推測をしたがるな』に従おう。
>>> import this (略) In the face of ambiguity, refuse the temptation to guess. (略)
IO境界でラップする
Unicode の取り扱いに関するたった一つのベストプラクティスは、IO境界 --
つまりファイルの読み書きや、ネットワーク通信、画面出力などのタイミングで変換することだ。
- 入力のタイミングで、str から unicode にデコード
- 出力のタイミングで、unicode から str にエンコード
- 内部処理は全て unicode 文字列として取り扱う
Python 2.6 から利用可能になった io モジュールを使えばラッピングを簡便にしてくれる。
- ファイル入出力
>>> import io >>> with io.open(FILEPATH, mode='w', encoding='utf-8') as file: ... file.write(u'あいう\n') ... 4L
- 標準入出力
標準出力を io.open() で開き直せば、適切なエンコーディングが選択される。
>>> import io, sys >>> stdout = io.open(sys.stdout.fileno(), 'w') >>> stdout.write(u'あいう\n') あいう 4L
io.open() の場合、encoding パラメータを指定しなくても、環境に応じてよしなに
エンコーディングを判断してくれるのが素晴らしい。
実際には後述の locale.getpreferredencoding() の値が参照されているようだ。
Python クックブック(1.22章)には codecs.lookup を使った以下のような方法も掲載されていた。
>>> import codecs, sys >>> old = sys.stdout >>> sys.stdout = codecs.lookup('utf-8')[-1](sys.stdout)
環境に応じたエンコーディングを取得する
Python が実行された環境の標準的なエンコーディングを動的に取得するにはどうすればよいか。
完全な方法ではないものの、以下の2種類のアプローチを順に試せば大半の環境で意図した動作となるようだ。
- ファイルの encoding 属性
属性が存在しない場合もあるので、getattr() を使ったほうが安全。 - 例) Mac (utf-8)
>>> import sys >>> getattr(sys.stdout, 'encoding', None) 'UTF-8'
- 例) Windows (SJIS)
>>> import sys >>> getattr(sys.stdout, 'encoding', None) 'cp932'
- 例) Solaris (SJIS)
>>> import sys >>> getattr(sys.stdout, 'encoding', None) >>>
- locale.getpreferredencoding()
- 例) Mac (utf-8)
>>> import locale >>> locale.getpreferredencoding() 'UTF-8'
- 例) Windows (SJIS)
>>> import locale >>> locale.getpreferredencoding() 'cp932'
- 例) Solaris (SJIS)
>>> import locale >>> locale.getpreferredencoding() 'PCK'
- 例) Mac (utf-8)
まとめ
- まず始めに、プログラムの設計段階で全てのIO境界を洗い出す
(図に書くのが一番よい)
- それぞれのIO境界において、どのエンコーディング(utf-8等)で
変換(decode, encode)を行うのか整理する
(同時に、環境に依存する部分や動的に取得する部分を明確にする)
- 明示的な変換(decode, encode)、明示的なエンコーディングの指定をする
(実行環境の defaultencoding (デフォルトはUS-ASCII) に依存しない実装をする)
(ライブラリ or 自作のラッパーを利用してもよい)
- それでも UnicodeDecodeError, UnicodeEncodeError が発生してしまったら、
変換前のデータ・データ型・変換しようとしたエンコーディングを再確認する
0 件のコメント:
コメントを投稿