Hatena::ブログ(Diary)

西尾泰和のはてなダイアリー

2012-01-25

他の言語に慣れた人がPythonを使ったときにつまずきがちな10のポイント

今日質問されて、以前Twitterで書いたのを思い出して、そして検索性が悪くて見つけ出すのに苦労した。こちらに転載しておく。詳細は気が向いたときに埋める。

オプション引数の評価タイミング

Rubyではオプション引数は関数が呼ばれるたびに評価される。

def foo() print "foo!" end
def bar(x=foo()) end

bar #=> foo! と出力される
bar #=> foo!
bar #=> foo!

Pythonでは関数の定義時に1回だけ評価される。

def foo(): print "foo!"
def bar(x=foo()): pass
#=> foo!と出力される

bar() #=> 何も出力されない
bar()

「引数が省略されたら今の日時」みたいな毎回評価したい場合はデフォルト値をNoneにしておいて「Noneだったら=省略されていたら」のif文を書けばOK。

def foo(when=None):
    if when is None: when = datetime.now()

内包表記はスコープを作らない

>>> x = 42
>>> [x + 100 for x in range(5)]
[100, 101, 102, 103, 104]

>>> x
4

また、下のようなコードでは、関数の中の自由変数を評価するタイミングで親のスコープでのiは2になっているので、どの関数を呼び出しても+2になってしまう。

>>> functions = [lambda x: x + i for i in range(3)]
>>> functions[0](100)
102

上記の「オプション引数は関数定義時に評価される」という挙動を利用したテクニックとして 「lambda x, i=i: x + i」 という書き方もある。でも、そもそも「パラメータの違いによって微妙に異なる挙動をする、類似した複数個の関数を作りたい」というモチベーションは、クラスを使って実装するのが素直なのでは。

class Func(object):
     def __init__(self, i): self.i = i
     def __call__(self, x): return x + self.i

変数はすべて参照

>>> a = []
>>> b = a
>>> b.append(1)
>>> a
[1]

代入ではコピーが行われない。コピーしたければfrom copy import deepcopyとか。

文字列とユニコード文字列は別物

Python2.0で文字列(str)と呼ばれるものは、バイトの列である。それとは別にユニコード文字列(unicode)があり、これは「Unicode コード単位」の列である。大雑把に言って文字列の各要素は8bitで、ユニコード文字列の各要素は16bitか32bitである。

# MacのTerminalにて(エンコーディングはUTF-8)
>>> "ほげ"
'\xe3\x81\xbb\xe3\x81\x92'
>>> u"ほげ"
u'\u307b\u3052'
# Windowsのコマンドプロンプトにて(エンコーディングはcp932)
>>> "ほげ"
'\x82\xd9\x82\xb0'
>>> u"ほげ"
u'\u307b\u3052'

なお、Python3からは"ほげ"をユニコード文字列、b"ほげ"をバイト列、と変更された。

unicodeとstrの暗黙の変換

結合などでのDecode

ユニコード文字列同士は結合しても何の問題もないが、ユニコード文字列とバイト列(str)を結合しようとすると、バイト列の方をユニコード文字列に変換しようとする。この時、エンコーディングがasciiであると仮定して変換するので日本人は大体UnicodeDecodeErrorで死ぬ。そのバイト列がUTF-8なのかSJISなのかはあなたしか知らないので、明示的に x.decode("utf-8") などとやってPythonに教えてあげなきゃダメ。

>>> u"ほげ" + u"ふが"
u'\u307b\u3052\u3075\u304c'

>>> u"ほげ" + "ふが" # 2つめがユニコード文字列ではない
---------------------------------------------------------------------------
Traceback (most recent call last)
----> 1 u"ほげ" + "ふが"

UnicodeDecodeError: 'ascii' codec can't decode byte 0xe3 in position 0: ordinal not in range(128)

結合の他に、比較などでもこの暗黙の変換が行われる。暗黙の変換に起因するトラブルを減らすために、Python3.0からは"ほげ"って書いたらユニコード文字列、b"ほげ"って書いたらバイト列、ユニコード文字列とバイト列は暗黙に変換しない、というルールに変わった。結合しようとすると「TypeError: Can't convert 'bytes' object to str implicitly」となる。明示的に変換せよ。

printでのEncode

逆に、ユニコード文字列をprintする際には、端末の文字コードにあわせてバイト列に変換が行われる。

print u"ほげ"
#=> ほげ と表示される

しかし、出力をファイルにリダイレクトすると、出力先が端末ではないので文字コードを取得できずasciiとみなされる。そのためこのスクリプトは出力をリダイレクトすると UnicodeEncodeError で死ぬ。

tmp$ python t.py
ほげ
tmp$ python t.py > out.txt
Traceback (most recent call last):
  File "t.py", line 2, in <module>
    print u"ほげ"
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)

出力先が端末かファイルかに関係なく特定のエンコーディングで出力したいのならば、標準出力をwrapしてしまうのが手軽。

import sys, codecs
sys.stdout = codecs.getwriter("utf-8")(sys.stdout)

see also: UnicodeDecodeError/UnicodeEncodeErrorに悩まないPython 2.x プログラミング - atsuoishimotoの日記

ソースコードのエンコーディング

ソースコードのエンコーディングを指定せずにソースコード中でASCII以外の文字を使うとシンタックスエラーになる。

SyntaxError: Non-ASCII character '\xe3' in file t.py on line 1, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details

他の言語では、ソースコードのエンコーディングをlatin-1などと仮定することがある。しかしその場合、たとえばソースコードがShift-JISで記述されていると「表」という文字の2バイト目がバックスラッシュと同一の文字になるので「"図表"」の2つめの引用符がエスケープされてしまうなどの問題が起きる。(see also: Wikipedia 2バイト目が5C等になりうることによる問題)

Pythonではそれを避けるために「ASCII以外の文字を使うならエンコーディングを指定すること」と規定している。エラーメッセージに詳細へのリンクが書かれているが、簡単に説明すると、ソースコードの1行目か2行目に正規表現 "coding[:=]\s*([-\w.]+)" にマッチする文字列があればよい。例:

# -*- coding: utf-8 -*-
print u"ほげ"

1要素のタプルにはカンマが必要

「一要素のタプルは(x, )って作るんですか?なんでカンマが必要なの?」

いいえ、そもそもタプルのカッコは飾り。式など(1 + 2) * 3のカッコと同じもの。本体はカンマであって、かつ最後のカンマは省略して問題ない場合は省略できる。1要素の時だけ、省略するとタプルだということがわからなくなるから省略できないわけだ。

>>> 1,
(1,)
>>> 1, 2,
(1, 2)
>>> 1, 2, 3,
(1, 2, 3)

言語リファレンスの式のリストより一部抜粋:

少なくとも一つのカンマを含む式のリストは、タプルになります。…単一要素のタプルを作りたければ、末尾にカンマが必要です。…空のタプルを作りたいなら、中身が空の丸括弧ペア: () を使います。


代入によってローカル変数が作られる

JavaScriptなどと異なり、代入によってローカル変数が作られる。なので、ネストした関数の中で外のスコープに存在する変数名対して代入を行なっても、内側のスコープで新しいローカル変数が作られるだけで、外の変数には影響を与えない。

def outer():
    x = 1
    def inner():
        x = 2 
        # ここで関数innerのローカル変数xが作られていて、
        # outerのxは書き換えられていない
    inner()
    print x

outer() #=> 1 と表示される。ほら書き換わってない

python3.0では「この名前はローカル変数ではない」という宣言(nonlocal)が可能になった。

def outer():
    x = 1
    def inner():
        nonlocal x
        x = 2 
    inner()
    print(x)

outer() #=> 2 と表示される。書き換わった!

また、代入が実行されるタイミングでローカル変数が作られるのではなく、関数の中で代入されうる変数はその関数のローカル変数となる。下の例で、xは関数fooの中で代入されうるのでfooのローカル関数になる。なのでprint xのタイミングで「local variable 'x' referenced before assignment」(ローカル変数xは代入する前に参照されている)というエラーになる。

x = 1
def foo():
    print x
    x = 2

foo() #=> UnboundLocalError: local variable 'x' referenced before assignment

整数がオーバーフローしない

整数の加減乗除やシフト演算で整数の範囲を超えた場合、Cなどのようにあふれたビットを捨てて範囲内に収めるのではなく、上限のない「長整数」に変換される。

>>> 1 << 63
9223372036854775808L
>>> -9223372036854775808 / -1
9223372036854775808L

>>> type(1)
<type 'int'>
>>> type(1L)
<type 'long'>

特にCで書かれた暗号化や擬似乱数生成のアルゴリズムは、オーバーフローによって切り詰められることを想定して書かれている場合があって、Pythonに逐語訳すると期待通りに動かない。

スライスはコピーを作る

スライスは左辺値を返す演算子ではない。

C++などに慣れている場合、xs[i:j] = ysというコードが「xs[i:j]が左辺値を返して、それからそれに対して代入演算子が実行される」というように見えてしまい、ys = xs[i:j]を実行した後でysを破壊的に更新した場合にxsに影響が伝わらないことに違和感を覚える。スライスの代入のタイミングでコピーが行われているように解釈してしまうかもしれない。

Pythonで xs[i:j] = ys を実行した場合は、iとjの値を持った「スライスオブジェクト」とysがxs.__setitem__メソッドに渡される。そしてリストの場合はxsを破壊的に書き換える実装になっている。一方でys = xs[i:j]の場合はxs.__getitem__メソッドにスライスオブジェクトが渡される。リストの場合はこれはその範囲の値をコピーした新しいリストを作る実装になっている。

C++と違って、代入演算子は再定義できないし、オブジェクトのメソッドでもない。PyObject *x, *y;でx = yしているイメージを持つのが適切かと思う。

おまけFAQ

良い入門書はないですか?

Python チュートリアル

中級者向けに良い解説書はないですか?

Python チュートリアルをもう一回読んでからPython 標準ライブラリ。以前は「ライブラリリファレンス」って訳されてたけど、それで検索してヒットするのは2.5なのでやめた方がいい。

chrだとかintだとかの説明はどこに?

ライブラリリファレンスの第2章: 2. 組み込み関数

文字列とかリストとかタプルとかについての説明はどこ?

ライブラリリファレンスに大概の事は書いてあるのだが、これは階層が深いせいで目次に出ていない…

ライブラリリファレンスの第5章 5. 組み込み型 の「シーケンス型」の節にまとめられている。

シーケンス型には 7 つあります: 文字列、Unicode 文字列、リスト、タプル、バイト配列 (bytearray)、バッファ、そして xrange オブジェクトです。

リストとタプルの違いは?

リストは書き換え可能で、タプルは書き換え不可能。とはいえこういう質問が出るということは書き換え不可能であることのメリットがわからないということかと思う。

たとえば、タプルは辞書のキーになることができ、リストはできない。これは「タプルは変更されないからハッシュ値を計算してもよい。リストは変更されるかもしれないからハッシュ値を計算してはいけない」という設計になっているから。

>>> x = (1, 2)
>>> {x: 1}
{(1, 2): 1}
>>> x = [1, 2]
>>> {x: 1}
TypeError: unhashable type: 'list'

Rubyでは書き換えることのできるArray(リスト的なもの)をHash(辞書的なもの)のキーにできるが、そのかわりに書き換えると値を取り出すことができない不整合な状態になる。

> x = [1, 2]
=> [1, 2]
> d = {x => 'hello'}
=> {[1, 2]=>"hello"}
> d[x]
=> "hello"

> x << 2
=> [1, 2, 2]
> d
=> {[1, 2, 2]=>"hello"}
> d[x]
=> nil

あと、慣習的にはリストは「同じ型のものがいくつかわからない個数入っているもの」(たとえば[1, 2, 3])、タプルは「同じ型とは限らないものが決まった個数決まった順番で入っているもの」(たとえば('Taro', 31, 'Osaka', 167.8, 67.8)。フィールドに名前がついていない手抜きのstruct)を表現するのに使われる。

関数呼び出しのfunc(*arg, **kw)の*とか**って何?

リストや辞書を展開して関数に渡す記法です。チュートリアルでは4.7.4. 引数リストのアンパックなどで解説されています。