UNISIA-SE Tech Blog

気まぐれお勉強日記

[Python] [基本] 高階関数と畳込み (map, filter, reduce)

1. 前提条件


このページでは、Pythonで使用する高階関数(map, filter, reduce)について基本的な使い方について記載する。

以下、必要な前提知識。

▼ 下記ページを理解していること。
[Python] [基本] 組込みデータ型の特性(immutable, mutable等)
[Python] [基本] 組込みデータ型まとめ (bool, int, float, complex)
[Python] [基本] 組込みデータ型まとめ (str, list, tuple, range, dict)
[Python] [基本] 組込みデータ型まとめ (set, bytes, bytearray, fileobject)

▼ Python3.6がインストールされていること。
このページでは、venvの仮想環境(Python3.6)上にNumPyをインストールした環境で、Python対話モード(Pythonインタプリタ)にて実装サンプルを記載している。

※ Python対話モードについては下記を参考。
Python対話モード:[Python] 対話モード (インタプリタ) の使用方法

※ 実装サンプルの注意。
Python対話モードの応答にコードハイライトの予約語である class が登場しているため、コメントや変数名など不統一なハイライト色となっているのでご了承のこと。

2. 高階関数とは


Pythonでは、関数もオブジェクトと同じように捉えるため、関数を引数や戻り値に指定したり、関数に対して別の関数やオブジェクトを代入することができる。

この性質を利用して、関数を引数に持ったり、関数を戻り値で返す関数を 高階関数(higher-order function) という。

以下、関数を引数に持つ場合戻り値に持つ場合引数と戻り値の両方に持つ場合 それぞれの簡単な実装サンプルを記載する。

▼ 関数を引数に持つ高階関数

$ python
 >>>
 >>> # 関数 p_func を引数に持つ、高階関数 h_order_func を定義
 >>> def h_order_func(p_func):
 ...     p_func()
 ...
 >>> # メッセージを出力する関数 print_msg を定義
 >>> def print_msg():
 ...     print('executed print_msg')
 ...
 >>> # 高階関数 h_order_func の引数に関数 print_msg を指定して実行
 >>> h_order_func(print_msg)
 executed print_msg
 >>>

▼ 関数を戻り値に持つ高階関数

$ python
 >>>
 >>> # 関数 add_param を戻り値に持つ、高階関数 h_order_func を定義
 >>> def h_order_func(x):
 ...     def add_param(y):
 ...         return x + y
 ...     return add_param
 ...
 >>> #  h_order_func(5)の戻り値である add_param(y) に 10 を指定して実行
 >>> h_order_func(5)(10)
 15
 >>>
 >>> #  ※ h_order_func(5) の 戻り値は、下記の add_param(y) の定義となり、x が 5 に置き換わっている状態となる
 ...     def add_param(y):
 ...         return 5 + y
 >>>

▼ 関数を引数と戻り値の両方に持つ高階関数

$ python
 >>>
 >>> # 関数 func を引数に持ち、戻り値に add_func 持つ、高階関数 h_order_func を定義
 >>> def h_order_func(func):
 ...     def add_func(x, y):
 ...         return func(x) + y
 ...     return add_func
 ...
 >>> # 5 を加算する関数 add_five を定義
 >>> def add_five(x):
 ...     return x + 5
 >>>
 >>> #  h_order_func(add_five)の戻り値である add_func(x, y) に 1, 2 を指定して実行
 >>> h_order_func(add_five)(1, 2)
 8
 >>>
 >>> #  ※ h_order_func(add_five) の 戻り値は、下記の add_func(x, y) の定義となり、関数 func(x) が 関数 add_five(x) に置き換えられた状態となる
 ...     def add_func(x, y):
 ...         return x + 5 + y
 >>>

以降、高階関数 map , filter, reduce を簡単な実装サンプルで解説する。

3. map(要素別の演算)


map(function, iterable) は、第2引数(iterable) に指定したイテレータ(各要素の反復処理ができるインターフェース)の各要素に対して、第1引数(function) を実行した結果をイテレータで返す。

▼ 実装サンプル

$ python
 >>>
 >>> # 第1引数の関数を定義(3倍にするだけ)
 >>> def triple(p):
 ...    return p * 3
 ...
 >>> # 第2引数のlist型を定義
 >>> list_a = [1, 2, 3, 4, 5]
 >>>
 >>> # mapを実行
 >>> result = map(triple, list_a)
 >>>
 >>> # map型のイテレータで返ってくる
 >>> print(result)
 <map object at 0x7f59b53cc5c0>
 >>>
 >>> # list型にキャストし、中身を確認
 >>> print(list(result))
 [3, 6, 9, 12, 15]
 >>>

▼ 上記の実装サンプルをラムダ式で書くと

$ python
 >>>
 >>> result = map(lambda x:x * 3, list_a)
 >>> print(list(result))
 [3, 6, 9, 12, 15]
 >>>

▼ 上記の実装サンプルでmapを使用せず内包表記で書くと

$ python
 >>>
 >>> # map(triple, list_a) と同義
 >>> result = (triple(p) for p in list_a)
 >>> print(list(result))
 [3, 6, 9, 12, 15]
 >>>

4. filter(指定要素の除外)


filter(function, iterable) は、第2引数(iterable) に指定したイテレータの各要素に対して、第1引数(function) の実行結果がTrueとなるイテレータを返す。

▼ 実装サンプル

$ python
 >>>
 >>> # 第1引数の関数を定義(正であればTrueを返す)
 >>> def is_plus(p):
 ...    return p > 0
 ...
 >>> # 第2引数のlist型を定義
 >>> list_a = [-2, -1, 0, 1, 2]
 >>>
 >>> # filterを実行
 >>> result = filter(is_plus, list_a)
 >>>
 >>> # filter型のイテレータで返ってくる
 >>> print(result)
 <filter object at 0x7f59b53cc668>
 >>>
 >>> # list型にキャストし、中身を確認
 >>> print(list(result))
 [1, 2]
 >>>

▼ 上記の実装サンプルを ラムダ式 で書くと

$ python
 >>>
 >>> result = filter(lambda x:x > 0, list_a)
 >>> print(list(result))
 [1, 2]
 >>>

▼ 上記の実装サンプルでfilterを使用せず 内包表記 で書くと

$ python
 >>>
 >>> # filter(is_plus, list_a) と同義
 >>> result = (p for p in list_a if is_plus(p))
 >>> print(list(result))
 [1, 2]
 >>>

5. reduce(畳込み演算)


reduce(function, iterable [, initializer]) は、畳み込み演算 と呼ばれ、第2引数(iterable) に指定したイテレータの2つの要素に対して、左から順に第1引数(function) を実行していき、これをイテレータの最終要素まで繰り返し、1つの結果を返す。

そのため、第1引数(function) の引数は、必ず2つ であることが前提となる。

第3引数(initializer) は、初期値を指定することができる。
初期値を指定した場合、最初の演算は、初期値第2引数(iterable) の先頭要素で 第1引数(function) を実行する。

内包表記 は、畳込み演算 に対応していないため、reduce は、内包表記で表現できない。

▼ 実装サンプル

$ python
 >>>
 >>> # functoolsからインポート
 >>> from functools import reduce
 >>>
 >>> # 第1引数の関数を定義(差を返す)
 >>> def minus(p1, p2):
 ...     return p1 - p2
 ...
 >>> # 第2引数のlist型を定義
 >>> list_a = [-5, -4, -3, -2, -1]
 >>>
 >>> # minus と list_a で reduce を実行
 >>> print(reduce(minus, list_a))
 5
 >>>
 >>> # 第3引数に -6 を指定
 >>> result = reduce(minus, list_a, -6)
 >>> print(result)
 9
 >>>

▼ 上記の実装サンプルを ラムダ式 で書くと

$ python
 >>>
 >>> result = reduce(lambda x, y: x - y, list_a)
 >>> print(list(result))
 5
 >>>

以上。



【参考文献】
金城 俊哉 (2018) 『現場ですぐに使える! Pythonプログラミング逆引き大全313の極意』株式会社昭和システム


Copyright UNISIA-SE All Rights Reserved.
s-hama@unisia-se.jp