Pythonでの日本語処理:Unicode型と文字列型
Pyhton の XML/HTML パーサ・ライブラリ BeautifulSoup を使って、Google の検索結果を整形する Python スクリプトを書いたところ、Python の日本語処理で UnicodeEncodeError、UnicodeDecodeError ではまった。いい機会なので、Python で日本語処理に関して、自分なりに整理してみる。
この記事は Windows での Python 2.5.1 で動作確認している。Python 3.x では改善しているかもしれないので、この記事を読む方はご注意を。Python 3.x については時間があれば確認したい。というより、早くバージョンアップしなさい!という感じですが。
[2009.09.22 追記]
Python 3.0 で Unicode まわりがかなり修正かかっていました。この記事を読む方は、Python 2.5.1 での日本語の取り扱いである、という点に留意してください。Python 3.0 以降だと、Unicode 型・文字列型ではなく、文字列型(= 今回の Unicode 型と同義かな)とバイト型となった模様。Python 3.0 以降にバージョンアップします。
サンプルコード
>>> a = u'ほげ' >>> type(a) <type 'unicode'> >>> b = 'ふが' >>> type(b) <type 'str'> >>> print a + b Traceback (most recent call last): File "<pyshell#4>", line 1, in <module> print a + b UnicodeDecodeError: 'ascii' codec can't decode byte 0x82 in position 0: ordinal not in range(128)
ん、UnicodeDecodeError なるエラーが生じる。このエラーの原因はなんぞや?
Python における文字列
後述の参考情報や、その他インターネットの情報、自分で試した結果から、Python における文字列の取り扱いを以下の図にまとめてみた。
Python で文字列を扱う場合、Unicode型と文字列型の 2 つの型が存在する。
- Unicode型
- 文字列をキャラクタとして扱う。'ほげ'という日本語文字列があった場合、len('ほげ')=2
- 文字列型
- 文字列をバイトとして扱う。'ほげ'という日本語文字列があった場合、len('ほげ')=4
Unicode 型はあくまで Python 内部の抽象概念であるため、Python から入出力する場合、入力元や出力先が分かる文字列型で文字列が扱われる。Python チュートリアルによると、Unicode 型は Python 2.0 から導入された概念であり、必ずしも Unicode 型を使用しなくてもよい模様。
日本語文字列のパターンマッチ等、日本語文字列をキャラクタとして扱う場合、Unicode型が適切と言える。また、インターネット上で公開されている Python パッケージでは、パッケージ内での文字列を Unicode 型として扱うものがある。このまとめを書くキッカケを作ってくれた BeautifulSoup も文字列を Unicode 型で扱う。Python で文字列を扱う場合、Unicode 型で扱う方が便がよいと言える。
Unicode 型と文字列型は相互に型変換が可能となっている。それぞれの型変換はエンコード、デコードという*1。
この型変換は明示的に記述せずとも暗黙に実行される。この暗黙の型変換の場合、Unicode のエンコード・デコード時には、文字コードとして US-ASCII(7bit ASCII) が指定されるのだ。暗黙の文字変換に US-ASCII が使用されることが、UnicodeDecodeError、UnicodeEncodeError の原因となる。
Python 2.0 から、プログラマはテキスト・データを格納するための新しいデータ型、Unicode オブジェクトを利用できるようになりました。 Unicode オブジェクトを使うと、Unicode データ (http://www.unicode.org/ 参照) を記憶したり、操作したりできます。また、 Unicode オブジェクトは既存の文字列オブジェクトとよく統合していて、必要に応じた自動変換機能を提供しています。
Python チュートリアル, 3.1.3 Unicode 文字列
(snip)
組込み関数 unicode() は、登録されているすべての Unicode codecs (COder: エンコーダ と DECoder デコーダ) へのアクセス機能を提供します。codecs が変換できるエンコーディングには、よく知られているものとして Latin-1, ASCII, UTF-8 および UTF-16 があります。後者の二つは可変長のエンコードで、各 Unicode 文字を 1 バイトまたはそれ以上のバイト列に保存します。デフォルトのエンコーディングは通常 ASCIIに設定されています。ASCIIでは 0 から 127 の範囲の文字だけを通過させ、それ以外の文字は受理せずエラーを出します。 Unicode 文字列を印字したり、ファイルに書き出したり、 str() で変換すると、デフォルトのエンコーディングを使った変換が行われます。
サンプルコードの UnicodeDecodeError とは?
サンプルコードでは、Unicode 型変数 a、文字列型変数 b を + 演算子で連結した場合、暗黙的に文字列型は Unicode 型に型変換される。文字列型から Unicode 型に型変換される際、文字列型はデフォルトエンコーディング(US-ASCII)に基づいて、Unicode 型に変換(デコード)される。US-ASCII は 7bit までの値しか扱えないため、1 バイトの値が 128-255 となると、範囲外であるとのことで エラーが生じる*2。英数字を扱うだけなら問題ないのだが、日本語といったマルチバイトが Unicode 型、文字列型に混在すると、UnicodeDecodeError、UnicodeEncodeError に度々遭遇することになってしまう。
サンプルコードの解決方法
解決方法1: Unicode 型、文字列型を適切な文字コードで型変換する
Unicode 型、文字列型が混在する場合、明示的にどちらかに型変換を実施する。
### 文字列型 -> Unicode 型 # print で出力するのは、Unicode 型となる #(厳密にはさらに型変換された文字列型) print a + unicode(b, 'utf-8', 'ignore') ### Unicode 型 -> 文字列型 # print で出力するのは、文字列型となる print a.encode('utf-8') + b
後述する解決方法2, 3 と比べると、Python コード単体できちんと解決するため、やはりこの解決方法が適切だろう。ただし、コード内部で常に Unicode 型、文字列型を意識する必要があるため、大規模プログラムを書く場合には現実解ではないかもしれない。この辺りは本職の方々にご意見がほしいところだ。主業務がプログラムを書くことではないので(^^;
解決方法2: Unicode オブジェクトのデフォルトエンコーディングを適切な文字コードに変更する
sitecustomize.py
import sys sys.setdefaultencoding('utf-8')
sitecustomize.py を設定した後の Python Shell での実行確認
>>> import sys >>> sys.getdefaultencoding() 'utf-8' >>> a = u'ほげ' >>> b = 'ふが' >>> print a + b Traceback (most recent call last): File "<pyshell#4>", line 1, in <module> print a + b UnicodeDecodeError: 'utf8' codec can't decode byte 0x82 in position 0: unexpected code byte # Windows の Python Shell で実行したら、また見事に UnicodeDecodeError を # 頂戴しました;; ただし、デフォルトエンコーディングが 'ascii' から # 'utf-8' となっていることが分かる。 # sys.setdefaultencoding('shift-jis') として再度 Python Shell で試す >>> import sys >>> sys.getdefaultencoding() 'shift-jis' >>> a = u'ほげ' >>> b = 'ふが' >>> print a + b ‚〓‚°ふが # Python Shell では結局文字化けしました。
Python では各 Python モジュールを読み込む際に、暗黙的に site モジュールを読み込む*3。この site モジュール内で、前述のsitecustomize.py が読み込まれる。Bash シェルにおける、.bashrc や .bash_profile といったイメージなんでしょう、きっと。本来であれば、sitecustomize.py ではなく、Python スクリプト内で sys.setdefaultencoding を呼び出せれば分かりやすくていいのですが、site モジュール内で sys.setdefaultencoding を del しているため、Python スクリプト内では setdefaultencoding を呼び出せない(なんてこった)。
個人で Python を利用するならこの解決方法でもいいように思うけど、コードを様々な環境下で利用することを想定した場合、環境依存である、この解決方法は問題となりそう。別の環境でいちいち sitecustomize.py を設定するのは面倒くさいし、他のコードの動作に影響を与える可能性もある。あと何より美しい解決策ではない(笑)
解決方法3:siteモジュールの読み込みをコントロールして、デフォルトエンコーディングを変更する
参考情報の http://hain.jp/index.php/tech-j/2007/12/03/p188 で紹介していた方法。
サンプルコードを以下のように修正して、c.py として保存する。
import sys sys.setdefaultencoding('utf-8') import site a = u'ほげ' b = 'ふが' c = a + b print c
python に -S オプションに付与し、c.py を指定し実行する。
C:\>python -S c.py ほげふが C:\>python -h usage: python [option] ... [-c cmd | -m mod | file | -] [arg] ... Options and arguments (and corresponding environment variables): -c cmd : program passed in as string (terminates option list) (snip) -S : don't imply 'import site' on initialization (snip) PYTHONCASEOK : ignore case in 'import' statements (Windows).
暗黙的に読み込まれる stie モジュールを -S オプションで読み込み無効にして、Python スクリプト内で自分で読み込ませる。読み込ませる前に sys.setdefaultencoding() でデフォルトエンコーディングを変更する。Unix 系で Python を使うなら、この方法が一番いいのではないかと思う。ただ、Windows だと関連付けで -S オプションを設定しないと意味がないため、解決方法2 と同様に -S を設定したことを忘れそう。
まとめ
Python の日本語処理を自分なりに整理できた気がする。ちょっとすっきりした。
今後文字列を扱う場合は、基本的に Unicode 型で扱うのがいいのだろう。入力があったら、まず Unicode 型に変換して格納すれば、少なくとも + 演算子の連結処理で Unicode[En|De]codeError に遭遇する機会も少なくなり、暗黙的な型変換が生じにくくなる。で、Python 内部から出力する場合、Unicode 型から文字列型に型変換することを必須とする。・・・手間のかかる子というのは確か。
sys.setdefaultencoding() を site モジュールが del しなければいい気もするんだけど。
参考情報
- PythonのUnicodeEncodeErrorを知る - HDEラボ
- UnicodeEncodeError の解説で一番納得できた解説。分かりやすいです!。Unicode 型、str 型の関係で encode, decode を混同しやすいところも同意。
- http://hain.jp/index.php/tech-j/2007/12/03/p188
- CGI実行する時の方法が書きやすそう
- http://www.amazon.co.jp/dp/4873112761
【収集用メールアドレス】:q1w2e3w2@gmail.com