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-8Pythonunicode文字列は違う」。

encodeというメソッドは
 unicode文字列 → str文字列
の変換を行なう。具体的には
 UCS2 → UTF-8
と変換する。このとき UTF-8unicode文字列じゃなくて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版


具体的にさっきのソースの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版




[合いの手担当]

[しゃべり担当]

それ使わなくても、printはPython2とPython3で変わったところなので、wrapper使えばラクできるだろ。
さっきのエラーを回避する方法:

  • byte
    例: byt = b'あいう'
    文字列ではなくバイト列を格納するデータ型になった。

Python(パイソン)を入門するぞ。

おう

Google App Engine (GAE) が使ってみたいので Python2.5を学ぶことにする。環境はLinuxUbuntu 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 以外はまともにインストールできない。

LinuxPythonソースとってきてコンパイルするのもありだけどバイナリ提供されてるならそのほうがラク
Ubuntu 8.04LTS なら最初からPython2.5が入っている。これにしよう。

というわけでUbuntu 8.04 ServerをVMwareの中にインストール。なぜServerかというと、今回はコンソールで動きさえすればよいからだ。Serverでないの(デスクトップ版)は、GUIOpenOffice.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を複数回押すことで、インデントの深さを変更可能。