Hatena::ブログ(Diary)

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

2012-01-25

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

今日質問されて、以前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 == 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要素のタプルにはカンマが必要

タプルはカッコではなくカンマで作られる。1要素のタプルは (x, )である。カッコは省略してもよい。

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

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

>>> (1)
1
>>> 1,
(1,)

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

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に逐語訳すると期待通りに動かない。


おまけFAQ

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

Python チュートリアル

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

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

文字列とかリストとかタプルとかについて調べようと思ったがライブラリリファレンスの目次をざっと読んでもそれらしき所がない…

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

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

m11mm11m 2012/01/27 21:47 >>類似した複数個の関数を作りたい」というモチベーションは、クラスを使って実装するのが素直なのでは。
関数の機能のみ使って,
functions = [(lambda i: (lambda x: x + i))(i) for i in range(3)]
と書くこともできます.

t98907t98907 2012/01/28 22:33 「文字列とユニコード文字列は別物」ってpytho 2.x系の話ですよね。

nishiohirokazunishiohirokazu 2012/02/11 13:19 > m11mさん
おすすめしません。

> t98907さん
そうですね、誤解を招かないように少し加筆しました。

投稿したコメントは管理者が承認するまで公開されません。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/nishiohirokazu/20120125/1327461670