2016年2月8日

functools.reduce()

高階関数の1つ、functools.reduce() を紹介します。

reduce() の第一引数は引数を2つ取る関数、第二引数はコンテナ、となります。
以下は具体的な例です。

>>> from functools import reduce
>>> from operator import mul

>>> c = [1, 2, 3, 4, 5]
>>> reduce(mul, c)
120
上記 reduce() の中で実際に行われている処理は、以下のようになります。
コンテナの各要素を関数でまとめ上げる、といった感じですね。
mul(mul(mul(mul(1, 2), 3), 4), 5)

reduce() を使わない一般的なループ文は以下のようになります。
>>> m = 1
>>> for i in c:
>>>     m *= i

>>> m
120
同じ処理の実装に3行必要となっていますので、reduce() の方がスッキリとした実装であると言えます。

書き方以外にも、reduce() は c[0] から計算を始めているのに対し、一般的なループ文の例では1行目で m の初期化を行っている点も相違点です。
reduce() の第二引数のサイズが0の時、および1の時には、それぞれ以下のように動作します。特にサイズが0の場合は例外発生するので要注意です。

# サイズが0ならエラー
>>> reduce(int.mul, [])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: reduce() of empty sequence with no initial value

# サイズが1なら[0]
>>> reduce(int.mul, [10])
10

以下は reduce() のもう少し実践的な使用例です。文字列の XORチェックサムを求めています。
>>> from functools import reduce
>>> from operator import xor

>>> s = "ABC"
>>> reduce(xor, (ord(i) for i in s))
64




以下余談です。

Python 2 では高階関数として map(), filter(), reduce() の3つがビルトイン関数として用意されていました。
しかし、Python 3 では reduce() のみが functools へと移されました。ビルトインから別モジュールへの移動ですから、事実上の降格です。

私にはこの決定が良い物とは思えません。
と言うのも、map() および filter() は内包表記を使うことで、同等の処理を実現できます。仮に map() と filter() が降格になるならば、それは「Python は内包表記を推している」という風に受け取られると思います。
しかし reduce() にはこれと言った代替手段は無く、reduce() を使わないとなると普通にループ文を書くしかありません。代替手段の無い reduce() こそ、ビルトインに残る方が良いと思うのですが、、、。
降格の一番の理由は、Guido氏(Python の開発者)が reduce() を「直感的でない」と否定的に見ていることのようです。

0 件のコメント:

コメントを投稿