2011-06-18 Pythonを学ぶ(1) HelloWorldでいきなりエラー (Python2とUnicode) 
プログラミング言語 Python(パイソン)を入門しようと Hello World を実行したら、いきなりエラーさ。
hello.py
# -*- coding:utf-8 -*- print(u'こんにちは')
出力:
$ python hello.py Traceback (most recent call last): File "hello.py", line 2, in <module> print(u'こんにちは') UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-4: ordinal not in range(128)
はっはっは
google: python2 unicode とかで調べると、ここはややこしい箇所だった。上のサンプルはPython2.5で試しているのだが、今はPython3も出て文法が変わったので、それもややこしさに拍車をかけている。
本『Pythonクックブック』『Pythonチュートリアル第2版』も参考にしたところ、以下のように理解した。
Python2の文字列は2種類
- str
例: text_str = 'あいう'
通常の文字列。中身はバイト列として扱われる。 - unicode
例: text_ucs = u'あいう'
Unicode文字列。中身はUCS2 or UCS4。
Python3の文字列は1種類
- 文字列
例: text_ucs = 'あいう'
以前のUnicode文字列と同じだが、いちいちソースに u"あいう" のuをつけなくてもよくなった。
今回はPython2を学習するので、Python3のことは考えない。
Python2の文字列のうちunicode文字列をそのままstdoutに出力すると、さっきのエラーが出る。
つまり、stdoutに出力するときはstr型に変換してから出力すればいい。
具体的には
1. unicode文字列を出力のときにstr型に変換
# -*- coding:utf-8 -*- print(u'こんにちは'.encode('utf-8'))
2. unicode文字列を使わず、最初からstr型を使う
# -*- coding:utf-8 -*- print('こんにちは')
出力はどちらも:
$ python hello.py こんにちは
後者のほうがシンプルに見える
しかし後者の方針はよくないんだ。
データをstr型だけで運用しようとすると文字列処理がうまくいかない。
こんなサンプルを作ってみた。
str_vs_unicode.py
# -*- coding:utf-8 -*- from types import * import re # str text_str = 'あいうウェブ画像' print(str(type(text_str))) print(text_str) print('len=%d' % len(text_str)) print(text_str[3]) text_mod = re.findall(r'([あ-お]+)', text_str) #print(str(type(text_mod))) print(text_mod) print(text_mod[0]) print('\n') # unicode text_ucs = u'あいうウェブ画像' print(str(type(text_ucs))) print(text_ucs.encode('utf-8')) print('len=%d' % len(text_ucs)) print(text_ucs[3].encode('utf-8')) text_mod = re.findall(ur'([あ-お]+)', text_ucs) print(text_mod) print(text_mod[0].encode('utf-8'))
出力
$ python str_vs_unicode.py <type 'str'> あいうウェブ画像 len=24 ? ['\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x82\xa6\xe3\x82\xa7\xe3\x83\x96', '\x94\xbb', '\x83\x8f'] あいうウェブ <type 'unicode'> あいうウェブ画像 len=8 ウ [u'\u3042\u3044\u3046'] あいう
strのほうは、
- 文字列の長さが文字数じゃなくてバイト数になってる
- text_str[4]みたくインデックス操作で文字列のなかから正しい日本語文字を取り出せない
- 正規表現の範囲マッチがおかしい。
Unicodeのほうは正しい。
日本語文字列に対して、インデックス操作したり正規表現の範囲マッチ使うことって、そんなに頻度ないけどね。
確かに頻度低いな‥。それらを回避すればstr文字列で通すこともできるな‥。
あと
.encode('utf-8') ってどういう意味?
UTF-8ってユニコードでしょ? unicode文字列をUTF-8にエンコード?
「UTF-8と Pythonのunicode文字列は違う」。
encode
というメソッドは
unicode文字列 → str文字列
の変換を行なう。具体的には
UCS2 → UTF-8
と変換する。このとき UTF-8はunicode文字列じゃなくてstr文字列だぞ。以下の2行は同じ意味になる。
# -*- coding:utf-8 -*- text_str = 'こんにちは' text_str = u'こんにちは'.encode('utf-8')
おまけに言うと、逆の decode
というメソッドもあって、
unicode文字列 ← str文字列
の変換を行なう。たとえば .decode('utf-8') の場合
UCS2(unicode文字列) ← UTF-8(str文字列)
の変換を行なう。
unicode()
という関数もdecodeと同じだ。つまり以下の3行は同じ意味だよ。
# -*- coding:utf-8 -*- text_ucs = 'こんにちは'.decode('utf-8') text_ucs = unicode('こんにちは') text_ucs = u'こんにちは'
ふーん‥‥。
しかしUnicode文字列出力のたびに いちいち「.encode('utf-8')」つけるのは面倒すぎるよ。
それは対策がある。本『Pythonチュートリアル第2版』p204で紹介されているwrapperを一度唱えれば、あとはそれつけなくてもよくなるんだ。
- 作者: GuidovanRossum
- 出版社/メーカー: オライリージャパン
- 発売日: 2010-02-22
- メディア: 単行本(ソフトカバー)
具体的にさっきのソースのUnicode部分を書き換えると、次のようになる。
# -*- coding:utf-8 -*- from types import * import re #Python2向けのencoding wrapper (『Pythonチュートリアル第2版』p204) import codecs, sys sys.stdout = codecs.getwriter('utf-8')(sys.stdout) # unicode text_ucs = u'あいうウェブ画像' print(str(type(text_ucs))) print(text_ucs) print('len=%d' % len(text_ucs)) print(text_ucs[3]) text_mod = re.findall(ur'([あ-お]+)', text_ucs) print(text_mod) print(text_mod[0])
へー。
ただしこれはstdoutに対してだけしか効果ないので、ファイルに出力するときは別にいちいち「.encode('utf-8')」つける必要ある。
ん?
つまり以下のはエラーになるんだ。
# -*- coding:utf-8 -*- #Python2向けのencoding wrapper (『Pythonチュートリアル第2版』p204) import codecs, sys sys.stdout = codecs.getwriter('utf-8')(sys.stdout) text_ucs = u'こんにちは' f = open('a.txt', 'wb') f.write(text_ucs) #エラーになる f.close()
出力
$ python uni15.py Traceback (most recent call last): File "uni15.py", line 9, in <module> f.write(text_ucs) UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-4: ordinal not in range(128)
f.write(text_ucs.encode('utf-8'))
にすればエラーはなくなる。
そうか
それを使わないにしろ、出力はwrapper関数使って一箇所にまとめておけば修正をラクできるだろ。
いずれにしろPython2はなかなか面倒なとこがあるな。
PHPのほうがよっぽど簡単だったりする。preg_replaceにuオプションをつけるとUTF-8対応になるとか、関数の使いこなしだけ気にしてればいいからな。
それでもPython2.5を使う理由は あるのか
Google App Engineを使ってみたいというのもある。
あとPerlよりはまともな言語を覚えたいからな。
Rubyは文法がな‥‥「end end end」とかワンライナーに書きたくないし、作者も「endは‥読みにくい構成に反対する圧力(Rubyはわざわざワンライナー書きにくくしてるんだよ!)」なんて言っている。だったらPythonよりRuby選ぶ意味は私には ない。
結局ワンライナーはPerlが一番効率いいんだけど、Perlはハッシュのポインタを配列で持とうとしたらデリファレンス地獄になるとか、複雑なデータ構造に対応するのが困難だ。Unicodeの扱いも面倒だし(Python2も同じほど面倒だけど、Python3では理想的に改善された)。
Pythonはワンライナー書けないし、ほかの言語に比べて記述量が多くて効率があまりよくないような感じだが、なにしろ広範囲に使われている‥‥GoogleさんやUbuntuLinux内部とかでバリバリ使われてて「Perlの次」のデフォルト言語になってしまっているからね。
node.jsはワンライナーも書けるらしいけどまだ力量は未知数だしバイナリパッケージも用意されていないなど面倒。
PHPは正規表現リテラルがなくて「バックスラッシュを2個つければいいのか? 3個か? 4個か? いちいち試すのかよ‥」みたいに面倒なのが文字列操作言語としてはヤな感じだ。PythonはそこはPHPよりずいぶんマシだ。
とにかく総合的にラクするには大勢に使われてて最初からインストールされてる可能性の高い言語が一番だ。
Pythonのバージョンに2.5を選ぶ理由は Google App Engineのためだけだな。
ほー
話を戻すけど、そもそも なぜUnicode文字列を出力するとエラーになるのさ? コンソールはUTF-8対応なんでしょ?
環境はUbuntu 8.04でコンソールの文字コードは LANG=ja_JP.UTF-8 にしてて、つまりコンソールはUTF-8対応してる。原因はPython2のデフォルトエンコーディングがASCIIということにあるらしいんだ。だから「ASCII範囲外の文字は出力できません」とかいうエラーを出すようだ。
試してみよう。
import sys print('default encoding=' + sys.getdefaultencoding()) print('file system encoding=' + sys.getfilesystemencoding()) # print(locale.getpreferredencoding()) #エラーになるのでコメント化
$ py uni16.py default encoding=ascii file system encoding=ANSI_X3.4-1968
ちなみにPython「3」ならデフォルトエンコーディングは UTF-8になったから、Unicode文字列をそのまま出力してもエラーにならないらしい。
そうなのか。じゃあ今やってるUnicodeのエラー対策はPython3では必要なくなるのかな
あと気がついたこと
- Pythonでは
print('\n')
が改行を出力する。Perl,PHPなどではシングルクォートで囲むと「バックスラッシュ と nを出力」するので戸惑う。Pythonではシングルクォートとダブルクォートは同じ役目。「バックスラッシュ と nを出力」するにはprint(r'\n')
のようにr(rawの略)をつける。 - Pythonには正規表現リテラル(Perlでいう
/foo.*/
のスラッシュ)がない変わりにr'foo.*'
とすればいい。正規表現で日本語をつかうならur'あ.*お'
のようにurを使えばよい。
そのほか詳しくは
- Python 2.5 日本語ドキュメント http://www.python.jp/doc/2.5/
- Python 2.5 英語ドキュメント http://docs.python.org/release/2.5.4/
のTurorial等を見ればよい。
この記事の途中でとりあげた書籍『Pythonチュートリアル第2版』の内容はその「Python 2.5 日本語ドキュメント」とほぼ同じ内容なのだが「Python3.1に対応している・訳文が別・Python2との違いの説明」など少し書籍のほうがよくなっている。
その書籍や下にある書籍『Python クックブック 第2版』はPDFでも購入できる。 http://www.oreilly.co.jp/ebook/
PDF版は
- 面倒なDRMがかかっていないのはよい
- 容量も4MBくらいで軽い
- 本に載ってるソースのコピペに対応した(2011年5月以降)
という利点があるが
- 各ページに購入者のメールアドレスが入っていた(2011年5月以降なくなったらしい)
- ページ番号が数十ページずれていて、目次や索引が使い物になってない(2011年3月に3冊購入してどれも同じだった。2011年5月以降は未確認)
- 目次の項目をクリックしても、そのページに飛ばない
という欠点もある。
- 作者: AlexMartelli
- 出版社/メーカー: オライリー・ジャパン
- 発売日: 2007-06-26
- メディア: 大型本