とっても遅延評価なPython3

はじめに

Python2.x系は関数型言語としては、遅延評価が弱かったのだが、Python3から幾らかのBuilt-in Functionが強力な遅延評価機能を導入したのでちょっと見てみる。

filter

Python2までは

  • 文字列・タプル→そのまま
  • それ以外→リスト

を返していた(つまりそのまま評価されていた)のが、Python3からfilterオブジェクトを返すようになり、必要になるまで評価されない。

ちなみにこのfilterオブジェクトだが、あまり面白いことはできない。__getitem__が無いのでslice取れないし、__len__メソッドが無いのでlen()で長さを直接知ることはできない。

まぁlenに関しては、関数型言語では、長さを測る方法が意外に高コストなので、それと同じ事情と思われるが、しかしfilterした結果の長さとか割と使う機会多そうなので残念。

そういうことをしたい場合はsum(1 for _ in fil)よりもlen(tuple(fil))の方が処理が高速だが、何れにしてもPython2時代の倍近く処理時間が伸びる。(ちなみにtupleとlistどちらを使っても、処理時間は殆ど変わらない)

map

Python2までは必ずリストを返していたのが、Python3からmapオブジェクトを返すようになった。mapオブジェクトの仕様としてはfilterオブジェクトと殆ど変わらない。
この仕様の為、低コストでタプルなど別のコンテナに入れやすくなっている。(Python2のようにリストを経由した場合は、オブジェクトを余計に生成することになり、比較的コストが重くなる)

zip

こちらもPython3からzipオブジェクトを返すようになった。zipオブジェクトそのものは上記2つと全く同じ仕様。まぁこちらに関してはlenは要らないと思うけど。(2に決まっている)

range

Python3のrangeはPython2.x系のxrangeに良く似て、リストではなくイテレータチックなrange型を返す。

ただxrangeと違いもある。Python3のrange型はタプルに準拠したメソッドを持ち、割と色々融通が効く。例えばスライスが取れて、range(8)[1:4]とかやると、range(1,4)という型が帰ってくる。タプルにできる操作は何でもできるので試してみると面白い。

内包表記

まず[]で生成する内包表記に関しては、従来通りにリストを生成する。従ってmapと違い遅延評価しないことになり、差が生じるようになった。()はこちらも従来通りgenerator型を生成する。(始めて知ったのだが)

あとPython3からはこれにsetとdictを生成する内包表記が追加された。

  • dict -> {x:func(x) for x in iterable}
  • set -> {x for x in iterable if x is not None}

リストを生成してから変換するよりは低コストな筈。ただ全体的に標準関数がiteratorオブジェクトを返すようになってる関係上、旨みは減ってるかも。

reduce

Python3の汚点(ぉ)。どうやら外されてしまったようだ。
「格好悪い」
というGuidoの発言だが、畳み込みのないPythonとか残念極まる。どうせbuilt-in関数の半分以上は使われないんだから残しておけば良かったものを…(ぶつぶつ)

ちなみにfunctoolsをimportすれば使えるので実用上大きな問題は無いと思われる。実際問題、Pythonはsum, min, maxが強力なこともあり、reduceを多用する言語ではないのも確か。

dir

dir()というメソッド一覧のリストを返すBuilt-in関数があるのだが、何故かこちらはリストを返すままで、遅延評価しない。まぁその方が検証には便利だし、そもそもリスト生成のコストが支配的になるほどデカいリストは生成されないだろうし、dirを速度クリティカルな部分で使うとか何を言ってるのか(ry

まとめ

実にPython3から更に関数型チックになって無限リストの類が扱いやすくなってるようだ。良きかな良きかな。ちなみにこれらの多くはPython2でもitertoolsに入っているので、捜してみるのもいいかもしれない。