作業自体は一日程度で終わりました。また移行したライブラリのPython2.7での動作は何も問題が無く、後方互換性もバッチリで感心しました。
今回はPython3向け修正作業で感じた、ZIP・MAP組み込み関数の注意点について記事にします。
ZIP関数、Python2での動作
まずPython2での動作を見てみます。>>> zip([1,2,3],[4,5,6],[7,8,9]) [(1, 4, 7), (2, 5, 8), (3, 6, 9)]
また、For文やリスト内包表記を使ってみます。
>>> z = zip([1,2,3],[4,5,6],[7,8,9]) >>> for i, j, k in z: ... print i, j, k 1 4 7 2 5 8 3 6 9 >>> [(i, j+k) for i, j, k in z] [(1, 11), (2, 13), (3, 15)]
ZIP関数、Python3での動作
同様にZIP関数をPython3で動作させてみます。>>> zip([1,2,3],[4,5,6],[7,8,9]) <zip object at 0x10d6968c8>
For文やリスト内包表記を試してみます。
>>> z = zip([1,2,3], [4,5,6], [7,8,9]) >>> for i, j, k in z: ... print(i, j, k) 1 4 7 2 5 8 3 6 9 >>> [(i, j+k) for i, j, k in z] []
For文は実行できましたが、リスト内包表記は空のリストが返ってきただけでした。
zipオブジェクトというのは、ジェネレータと同様の遅延評価の機能を持っているそうですが、複数回の利用はできないみたいです。
ZIP関数をPython2と同様の動作に記述するには
Python3で動作させるには、工夫が必要です。3つ案があります。【案1】同じ記述を2つする
>>> for i, j, k in zip([1,2,3], [4,5,6], [7,8,9]): ... print(i, j, k) 1 4 7 2 5 8 3 6 9 >>> [(i, j+k) for i, j, k in zip([1,2,3], [4,5,6], [7,8,9])] [(1, 11), (2, 13), (3, 15)]
【案2】リストやタプルに変換する
>>> z = list(zip([1,2,3], [4,5,6], [7,8,9])) >>> for i, j, k in z: ... print(i, j, k) 1 4 7 2 5 8 3 6 9 >>> [(i, j+k) for i, j, k in z] [(1, 11), (2, 13), (3, 15)]
【案3】itertools.teeを使用
>>> import itertools >>> z = zip([1,2,3], [4,5,6], [7,8,9]) >>> z1, z2 = itertools.tee(z) >>> for i, j, k in z1: ... print(i, j, k) 1 4 7 2 5 8 3 6 9 >>> [(i, j+k) for i, j, k in z2] [(1, 11), (2, 13), (3, 15)] >>>
参考: itertools.tee(iterable, n=2)
他にもあるかもしれませんが、取り敢えず、これだけ挙げてみました。
MAP関数、Python3での動作
Python3では、MAP関数もZIPと同様の動作になります。
>>> map(lambda i,j,k: (i, j+k) , [1,2,3], [4,5,6], [7,8,9]) <map object at 0x10d69a2e8>
>>> z = map(lambda i,j,k: (i, j+k) , [1,2,3], [4,5,6], [7,8,9]) >>> for i in z: ... print(i) (1, 11) (2, 13) (3, 15) >>> [i for i in z] []
また、Python2 では最初の引数に None を指定すると、最大の配列数に長さを合わせて結合してくれました。しかし、Python3では使用するとエラーになります。
Python2
>>> map(None, [1,2,3], [4,5], [7]) [(1, 4, 7), (2, 5, None), (3, None, None)]
Python3
>>> list(map(None, [1,2,3], [4,5], [7])) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'NoneType' object is not callable
MAP関数をPython2と同様の動作に記述するには
MAP関数も、ZIP同様の解決策を適用可能です。
【案1】同じ記述を2つする
>>> for i in map(lambda i,j,k: (i, j+k) , [1,2,3], [4,5,6], [7,8,9]): ... print(i) (1, 11) (2, 13) (3, 15) >>> [i for i in map(lambda i,j,k: (i, j+k) , [1,2,3], [4,5,6], [7,8,9])] [(1, 11), (2, 13), (3, 15)]
【案2】リストやタプルに変換する
>>> z = list(map(lambda i,j,k: (i, j+k) , [1,2,3], [4,5,6], [7,8,9])) >>> for i in z: ... print(i) (1, 11) (2, 13) (3, 15) >>> [i for i in z] [(1, 11), (2, 13), (3, 15)]
【案3】itertools.teeを使用
>>> import itertools >>> z = map(lambda i,j,k: (i, j+k) , [1,2,3], [4,5,6], [7,8,9]) >>> z1, z2 = itertools.tee(z) >>> for i in z1: ... print(i) (1, 11) (2, 13) (3, 15) >>> [i for i in z2] [(1, 11), (2, 13), (3, 15)]
【案4】 itertools.zip_longestを使用
Python2の map(None, [1,2,3], [4,5], [7]) と同じ動作をさせる方法ですが、 itertools.zip_longest を使うと良いそうです。
>>> import itertools >>> list(itertools.zip_longest([1,2,3], [4,5], [7])) [(1, 4, 7), (2, 5, None), (3, None, None)]
参考:itertools.zip_longest
最後に
ZIPやMAP関数の動作の違いは、Python2のコードをPython3で動かしたらエラーになる訳ではないので、発覚しにくいです。仕様の違いを知らないと悩むことになるので、気を付けたいですよね。
参考
Cannot unpack a zip object multiple times
Python 3 vs Python 2 map behavior