2015年9月25日

python の加算代入演算子(+=)(プラスイコール)の注意点

python の一部のオブジェクト(list 等)では、「a = a + b」と「a += b」が同一視できない。
他言語経験者が思わぬバグを埋め込んでしまいそうな罠仕様・・・


>>> A = a = [1]
>>> a = a + [2]
>>> a, A, a is A
([1, 2], [1], False)
>>> # list の加算(+)は、元の a を変更しない
... # 加算の結果(新しい領域)を a に代入しているので、a と A は別オブジェクト
...

>>> B = b = [1]
>>> b += [2]
>>> b, B, b is B
([1, 2], [1, 2], True)
>>> # list の加算代入(+=)は、元の b を変更する
... # 加算代入しても新しい領域は割り当てられないので、b と B は同一オブジェクト
...

この理由は、list には 加算代入演算子(+=)用の __iadd__ メソッドが定義してあり、加算演算子(+)用の __add__ メソッド とは異なる動作になっているため。
※ list.__iadd__ の動作は list.extend と同じ。

__iadd__ メソッドが定義されてないオブジェクト(tuple 等)は、加算代入でも __add__ メソッドが使用される。
つまり、「a = a + b」と「a += b」 が同一視できる。

組み込みオブジェクトの大半には __iadd__ メソッドが定義されていないため、基本的に list とそのサブクラスを扱う際に注意すればいい。
加算代入演算子(+=)だけでなく、乗算代入演算子(*=)なども同様に注意。
ちなみに、__i???__ メソッドは、ミュータブルなオブジェクトに実装されるものらしい。

参考URL

2015年9月24日

python の代入は値を返さないけど多重代入は可能

python の代入は値を返さないので、下記のような書き方はエラーになる。

>>> # ファイルから少しずつ読み込んで処理
...
>>> f = open('hoge.txt', 'rb')
>>> while chunk = f.read(4096):
  File "", line 1
    while chunk = f.read(4096):
                ^
SyntaxError: invalid syntax

だけど、多重代入 (マルチターゲット代入 / chained assignment) はできる。

>>> a = b = 'test'
>>> a, b
('test', 'test')

何となく納得いかない感があるけど、多重代入だけは特別らしい。

下記のような書き方でも多重代入は可能。(自分がかつてやっていた方法)
左辺変更時に修正点多いし、あまりリーダブルじゃないと思うので使わない方がいい・・・

>>> a, b = ('test',) * 2
>>> a, b
('test', 'test')

おまけ

最初の例を綺麗に書きたい場合、iterfunctools.partial を組み合わせて使う。

>>> # ファイルから少しずつ読み込んで処理
...
>>> f = open('hoge.txt', 'rb')
>>> for chunk in iter(functools.partial(f.read, 4096), b''):
...    print(chunk)
...

>>> # partial の代わりにラムダ式でも可能
...
>>> for chunk in iter(lambda: f.read(4096), b''):
...    print(chunk)
...

2015年9月7日

python の等価演算子(==)と bool / 数値型

python の == 演算子は割と厳密で、公式ドキュメントにもあるとおり、数値型を除いて異なる型同士は等価にはならない。
javascript みたいに 1 == "1" が成立したりしない。
そのためか、python には === 演算子がない。

ただし、注意する点が 1 つあって、bool 型が int 型のサブクラスで、「True = 1」「False = 0」であること。
つまり、bool 型も数値型に含まれることになる。
そのため、下記のような結果になる。

>>> isinstance(False, int)
True
>>> isinstance(True, int)
True
>>> False == 0
True
>>> True == 1 == float(1) == complex(1)
True
>>> [True, False] == [1, 0]
True
>>> (True, False) == (1, 0)
True

上記を考慮して実装すれば基本的に問題ないのだが、どうしても厳密比較したい場合、対 bool 型であれば is 演算子を使う。

>>> value = True
>>> value is True
True
>>> value = 1
>>> value is True
False
対数値型であれば値の比較に加えて型の比較が必要になる。

>>> def isnum(value):
...   return type(value) in (int, float, complex)
...
>>> value = 1
>>> isnum(value) and value == 1
True
>>> value = True
>>> isnum(value) and value == 1
False

余談だが、python2 系では、True と False に値を代入して書き換えることができる。
割と危ない仕様・・・
python3 からはちゃんとエラーになってくれる。