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() で開き直せば、適切なエンコーディングが選択される。
io.open() の場合、encoding パラメータを指定しなくても、環境に応じてよしなに>>>
import
io, sys
>>> stdout
=
io.
open
(sys.stdout.fileno(),
'w'
)
>>> stdout.write(u
'あいう\n'
)
あいう
4L
エンコーディングを判断してくれるのが素晴らしい。
実際には後述の 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 件のコメント:
コメントを投稿