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種類
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版は
という利点があるが
- 各ページに購入者のメールアドレスが入っていた(2011年5月以降なくなったらしい)
- ページ番号が数十ページずれていて、目次や索引が使い物になってない(2011年3月に3冊購入してどれも同じだった。2011年5月以降は未確認)
- 目次の項目をクリックしても、そのページに飛ばない
という欠点もある。
- 作者: AlexMartelli
- 出版社/メーカー: オライリー・ジャパン
- 発売日: 2007-06-26
- メディア: 大型本
それ使わなくても、printはPython2とPython3で変わったところなので、wrapper使えばラクできるだろ。
さっきのエラーを回避する方法:
- byte
例: byt = b'あいう'
文字列ではなくバイト列を格納するデータ型になった。
Python(パイソン)を入門するぞ。
おう
Google App Engine (GAE) が使ってみたいので Python2.5を学ぶことにする。環境はLinux。Ubuntu 8.04LTS なら最初からPython2.5が入っている。環境を用意しておく(後述)。
ふむ
さてPythonを学んでいこう。といってもプログラミングの文法入門なんてすでに『型』がある。
人によって多少ズレはあるだろうけど‥
1. まずは Hello World.
2. 変数(数値、文字列)
3. 分岐
4. 配列(+ハッシュ)
5. ループ
6. 関数(+クラス)
7. ファイル入出力
6. 例外とかいろいろ
まあこんなとこ。
まずはHello Worldしよう。
# -*- coding:utf-8 -*- print(u"こんにちは\n")
出力は‥
Traceback (most recent call last): File "uni01.py", line 2, in <module> print(u"こんにちは\n") UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-4: ordinal not in range(128)
対策:
uを削除。これ(内部でstrとして運用)はRegExpの動作がへんになるしオススメできない。
# -*- coding:utf-8 -*- print("こんにちは\n")
これ(内部ではUnicode(UCS2orUCS4文字列)、外部入出力のときにstrに変換)が普通。
# -*- coding:utf-8 -*- ucs = u"こんにちは\n" # encode() : unicode -> str(byte) byte = ucs.encode('utf-8') print(byte)
ラッパーかます。出力するとき変換しなくても自動でしてくれる。
# -*- coding:utf-8 -*- #Python2向けのencoding wrapper (『Pythonチュートリアル第2版』p204) import codecs, sys sys.stdout = codecs.getwriter('utf-8')(sys.stdout) ucs = u"こんにちは\n" print(ucs)
日本語のドキュメント http://www.python.jp/doc/2.5/
英語 http://docs.python.org/release/2.5.4/
といっても、もはやPython3の時代。Python3も押さえておきたいけど、GAEが切り替わるのは今年はなさそうなのであえてPython2.5 onlyで。
さてWindowsのコマンドプロンプトでLL(ライトウェイト・ランゲージ、ラノベ的にいうとララン‥なんて言わないか)なんて使っていられないので LinuxとかCygwinでPython2.5を使いたい。CygwinはNetInstallerが最新にしか対応してないので、最新版 Python2.6 以外はまともにインストールできない。
LinuxでPythonソースとってきてコンパイルするのもありだけどバイナリ提供されてるならそのほうがラク。
Ubuntu 8.04LTS なら最初からPython2.5が入っている。これにしよう。
というわけでUbuntu 8.04 ServerをVMwareの中にインストール。なぜServerかというと、今回はコンソールで動きさえすればよいからだ。Serverでないの(デスクトップ版)は、GUIやOpenOffice.orgなど必要ないものを大量に入れてHDD容量とメモリをムダに消費して動作速度も遅くなる。
といってもインストールしただけでHDDイメージの大きさが600MB。VMwareToolsを手動で入れたら1GB突破してしまった。あまりHDD消費の節約にはなってないな。
インストール: 言語はEnglish。地域はOther→Japan。キーボードはJapan。パッケージはOpenSSH Serverのみ選択。
インストール後にVMwareToolsを手動で入れる。Windowsの某フォルダを共有フォルダにする。
apt-get install emacs22-nox lv zshなど実行。
Emacsの設定は共有フォルダ経由でコピー。
http://jesselegg.com/archives/2010/02/25/emacs-python-programmers-part-1/
を見て以下の設定を付け加えた。
;;; bind RET to py-newline-and-indent (add-hook 'python-mode-hook '(lambda () (define-key python-mode-map "\C-m" 'newline-and-indent)))
以下の設定は don't と入力するつもりが don't' になるわけで、ヤリスギと思ったので
未導入。
;;; Electric Pairs (add-hook 'python-mode-hook (lambda () (define-key python-mode-map "\"" 'electric-pair) (define-key python-mode-map "\'" 'electric-pair) (define-key python-mode-map "(" 'electric-pair) (define-key python-mode-map "[" 'electric-pair) (define-key python-mode-map "{" 'electric-pair))) (defun electric-pair () "Insert character pair without sournding spaces" (interactive) (let (parens-require-spaces) (insert-pair)))
Emacs22では
Tabを複数回押すことで、インデントの深さを変更可能。