Address
304 North Cardinal St.
Dorchester Center, MA 02124

Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM

Pythonのfunctools.reduce

【Python入門】functools.reduceの基本の解説

Ptyhonの便利なfunctools.reduceの使い方と基本についての解説

今回はPythonの便利なモジュールの1つ、functooolsモジュールが提供するreduceという機能の紹介です。

functools.reduceとは?

公式ドキュメント:functools.reduce

この公式ドキュメントでは、次のように解説が書かれています。

iterable の要素に対して、iterable を単一の値に短縮するような形で 2 つの引数をもつ function を左から右に累積的に適用します。

全く意味がわかりませんね。ですがあとの具体例で意味が明確に理解できるようになります。現段階では次の2点に注意を払っておいください。

  • 2つの引数を持つ関数が必要
  • イテラブルの各要素に対して、その関数を「累積的」に適用

これを意識しておいてもらえると、下記の具体例が理解しやすくなると思います。

さて、次に使い方を見ておきましょう。このような形となります。

functools.reduce(第1引数, 第2引数[, 第3引数])
第1引数第2引数で設定するイテラブルオブジェクトの各要素に適用する関数
第2引数第1引数で設定する関数が適用されるイテラブルオブジェクト(イテラブルオブジェクトの典型的な例の1つがリストオブジェクトです)
第3引数(省略可能)初期化処理

なおイテラブル(オブジェクト)についての解説は「【初心者にもわかりやすく】Pythonのイテレータについて、その基本とfor文の仕組みを解説」という記事で解説していますので、ぜひご覧ください。このブログでも結構アクセス数の多い人気記事となっています。

では以下、具体例としてのコードを見ていきましょう。

具体例その1

さて、上でfunctools.reduceの引数について次のよう書きました。

  • 2つの引数を持つ関数が必要
  • イテラブルの各要素に対して、その関数を「累積的」に適用

これをわかりやすく理解するために次のようなコードを作りました。見てください。

def func(x, y):
    """

    Args:
        x : 1つ前の結果が入る
        y: イテラブルの次の要素が入る

    Returns:
        引数x, yのタプルを返す
        
    """
    return (x, y)


functools.reduce(func, [1])
functools.reduce(func, [1, 2])
functools.reduce(func, [1, 2, 3])
functools.reduce(func, [1, 2, 3, 4])

今回はfunctools.reduceについて、

第1引数funcという名前の関数(2つの引数x, y)を持つ
第2引数リスト
第3引数(省略可能)なし

このように設定しています。functools.resuceの第1引数となる関数は、2つの引数を持たなければなりません。したがって、上のコードでもx, y という2つの引数を設定します。

ではコードの15行目から1行ずつ見ていきましょう。

イテラブルの要素が1つだけの場合

さてまず最初に15行目で、

functools.reduce(func, [1])

これを実行すると、

1

となります。これは、第2引数のイテラブルオブジェクト(今回はリストとして[1])がただ1つの要素しか持っていない場合に、

initializer が与えられておらず、 iterable が単一の要素しか持っていない場合、最初の要素が返されます。

公式ドキュメント:functools.reduce

という仕組みなっているからです。

「累積的に適用」の意味とは?

次に16行目のコード、

functools.reduce(func, [1, 2])

これを実行すると、

(1, 2)

単純なタプルとなります。このタプルの内容は、

第1要素1
(これはfunctools.reduce(func, [1])の結果)
第2要素2

となっています。

続いて17行目の、

functools.reduce(func, [1, 2, 3])

ですが、これを実行すると、

((1, 2), 3)

このように、「(◯, 3)」という2つの要素からなるタプルがまた出来上がりました。このタプルの要素については次のようになっています。

第1要素(1, 2)というタプル
(これはfunctools.reduce(func, [1, 2])の結果)
第2要素3

ここまでの解説でもうおわかりかもしれませんが、最後に、

functools.reduce(func, [1, 2, 3, 4])

これを実行すると、

(((1, 2), 3), 4)

となり、これも結局は「(◯, 4)」という2つの要素からなるタプルにしかすぎず、

第1要素((1, 2), 3)というタプル
(これはfunctools.reduce(func, [1, 2, 3])の結果)
第2要素3

という構造になっています。すなわち、functools.reduceは、

イテラブルオブジェクト(今回はリスト)の第1要素に対して処理を実行
↓
その結果に対して、イテラブルオブジェクトの第2要素について処理を実行
↓
その結果に対して、イテラブルオブジェクトの第3要素について処理を実行
↓
その結果に対して、イテラブルオブジェクトの第4要素について処理を実行
↓
以下略

という仕組みになっています。1つ前の処理の結果を利用(前提とする)という点が「累積的」という意味ですね。

以上のことを理解していただければ、次のコード例2も理解が容易になるでしょう。

コード例2

import functools

"""
functools.reduce(function, iterable[, initializer])
"""

# iterable が空の場合には標準の値になる
# オプションの initializer が存在する場合、まず最初にinitializerがイテラブルの先頭として処理される
functools.reduce(lambda x, y: x+y, [], 4)  # ==> 4

# initializer が与えられておらず、かつ iterableが単一の要素しか持っていない場合、最初の要素が返される
functools.reduce(lambda x, y: x+y, [1])  # ==> 1

# 左引数 x は累計の値になり、右引数 y は iterable から取り出した更新値になります
functools.reduce(lambda x, y: x+y, [1, 2])  # ==> 3
functools.reduce(lambda x, y: x+y, [1, 2, 3])  # ==> 6
functools.reduce(lambda x, y: x+y, [1, 2, 3, 4])  # ==> 10
functools.reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])  # ==> 15

functools.reduce(lambda x, y: x+y, [11, 12, 13, 14, 15])  # ==> 65

今回はfunctools.reduceの引数を次のようにしています。

第1引数ラムダ関数
第2引数リスト
第3引数(省略可能)4

今回はfunctools.reduceの第3引数として初期化の処理を担当するもの(initializer:イニシャライザ)として、「4」を設定しています。このようにinitializerが設定されると、まずそれが第2引数のイテラブルの最初の要素として設定されます。結果、

functools.reduce(lambda x, y: x+y, [], 4)  

を実行すると、

4

という結果になります。

それ以外の部分についてはコード内のコメントと、上の具体例1で解説した内容の通りとなります。

    関連記事:functools.partialについて

    Pythonを最速でマスターできるおすすめ動画講座5選