Hatena::ブログ(Diary)

itouhiroメモ

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)

[合いの手担当] はっはっは


f:id:itouhiro:20110618211325g:image


[しゃべり担当] 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を一度唱えれば、あとはそれつけなくてもよくなるんだ。


Pythonチュートリアル 第2版

Pythonチュートリアル 第2版

  • 作者: 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を使えばよい。

そのほか詳しくは

の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月以降は未確認)
  • 目次の項目をクリックしても、そのページに飛ばない

という欠点もある。




Python クックブック 第2版

Python クックブック 第2版

  • 作者: AlexMartelli
  • 出版社/メーカー: オライリー・ジャパン
  • 発売日: 2007-06-26
  • メディア: 大型本

トラックバック - http://d.hatena.ne.jp/itouhiro/20110618