【Pythonステップアップ!】高階関数filterの便利な使い方

こんにちは。フリーランスのフクロウです。

いろんなプログラミング言語で実装されている機能の一つにfilter(フィルター)と言うものがあります。

これはリストのようなものから条件にあった要素のみを取り出す操作になります。

Pythonだけでなく、他の様々な言語で役に立つ機能なので是非とも使い方を覚えておきましょう。

この記事では、

  • そもそもfilterとは何なのだろう
  • filterで条件にあった要素のみを抽出するいろいろな方法

などの基本的におさえておきたい内容から、

  • Pythonユーザーがよく使うリスト内包表記との比較

といった発展的な内容についても説明していきます!

それでは見ていきましょう!

※ この記事のコードはPython 3.7, Ubuntu 18.04で動作確認しました。

本記事を読む前に、Pythonがどんなプログラミング言語なのかをおさらいしておきたい人は次の記事を参考にしてください。

→ Pythonとは?特徴やできること、活用例をわかりやすく簡単に解説

なお、その他のPythonの記事についてはこちらにまとめています。

目次

filterとは

filter関数とはリストのような複数の要素をもったオブジェクトから、条件にあった要素のみを取り出す操作を実装したものです。

Pythonに実装されたfilter関数は、与えられた条件がTrueである要素のみを取り出してくれます。

この条件が複雑な場合は複数の条件式を書く必要があります。
複数条件の書き方については

【Python if文の基本編】条件分岐を3つのステップで簡単理解
更新日:2024年4月10日
【Python if文の応用編】or・and・not演算子の使い方を理解しよう
更新日:2024年4月10日
【Python入門】今更聞けない!if else文の使い方まとめ
更新日:2024年3月1日

を参考にして下さい。

このとき、filterに渡す「条件」が関数なので、一般に高階関数と呼ばれている機能の一つに数えられます。

高階関数、聞き慣れない方も多い用語だと思います。

LispやHaskellといった「関数型言語」を勉強していると出てくる概念なのですが、要するに関数を受け取る関数か返り値として関数を返す関数の事を指します。

普通、関数の引数には何かしらの値を持ったオブジェクトを渡しますね。
しかし中には関数を受け取る関数もあるんです!

この記事で説明しているfilterもこの高階関数に含まれる機能なんです。

この他にはmapなどの名前がよく挙げられますが、これについてはまた別の記事で説明しましょう。

filterの使い方

filterとは何か、高階関数とは何かについて理解できたでしょうか?

仰々しい名前と裏腹に、意外と簡単な機能であることがわかっていただけたら嬉しいです。

基本的な概念について説明が終わったので、次にここでは実際の使い方について紹介していきます。

条件にあった要素のみを抽出する

まずは一番簡単な例として、「数字のリストから偶数の数字のみを取り出す操作」を例に見ていきましょう。

これを実装するには、

  • 普通の関数を使った方法
  • 無名関数を使った方法

の2つの方法が考えられます。

関数を使う

まずは偶数を評価する関数を作りましょう。

def even_evaluator(x:int)->bool:
    """この関数は引数が偶数ならばTrue, 
    奇数ならばFalseを返します
    """
    return x % 2 ==0

この関数は引数が偶数ならばTrue, 奇数ならばFalseを返します。

他のサムライの記事を読んでくださっている方であれば、関数についているタイプヒントについても気づいているでしょう。

ここでは、「この関数は引数xにint型オブジェクトを受け取ります。そしてbool型の返り値を提供します」ということが読み取れますね。
では実際に使ってみましょう。

print(even_evaluator(18))
print(even_evaluator(91))

これを実行すると

[実行結果]

True
False

と表示されます。この関数がちゃんと偶数と奇数を判定してくれることがわかりました!

ではこれをfilter関数で使ってみましょう。

filter関数は

  • 第一引数に条件を判定する関数を(この関数の返り値はbool値(TrueかFalse))
  • 第二引数に元になるシーケンスを(listなど)

を受け取ります。

なので第一引数に先程作った関数を、第二引数に今から作る0~9の要素を持ったリストを渡してみます。

num_list = list(range(10)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
even_list = filter(even_evaluator, num_list)
print(list(even_list))

これでfilter関数は偶数のみを取り出してくれるはずです。

さて、printの際にlist(even_list)としているのはなぜでしょう。

これはこうしないとprintされるものが欲しいリストではなく、<filter at 0x1fcbeb97b70>などのiterator objectになってしまうからです。

結果を見てみましょう。

[実行結果]

[0, 2, 4, 6, 8]

ちゃんと偶数のリストが取り出されたことが確認できました!

このようにあるシーケンスから条件にあった要素を抽出し、別のシーケンスを作ることが出来るのがfilterになります!

lambda式を使う

先ほどの例では条件を指定するのに、わざわざ関数を作っていましたね。

ですが簡単な条件であればもっと簡単に、無名関数lambdaを使って指定してあげることもできます

even_evaluator2 = lambda x:x % 2 ==0

これで前の章で作った関数と同じ機能を持ったlambda式をつくることができました。

このように何かの変数(ここではeven_evaluator2)に束縛することもできますが、filter関数の第一引数にそのまま渡すこともできます

実際に使ってみましょう。

num_list2 = list(range(10,20)) # [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
even_list2 = filter(lambda x:x % 2 ==0, num_list2)
print(list(even_list2))

このコードを走らせると、

[実行結果]

[10, 12, 14, 16, 18]

このように先ほどと同様に偶数のみのリストを作ることができました!

簡単な条件であればlambda式で書いた方が読みやすくなります。
便利な機能なので是非試してみてください!

リスト内包表記との比較

最後にfilterの機能をリスト内包表記で試してみましょう。

リスト内包表記はPythonだけではなく、Haskellなどの純粋関数型言語や、Juliaといった新しい言語でも取り入れられている機能です。

先程の機能をリスト内包表記で実装すると以下のようになります。

num_list3 = list(range(20,30)) # [20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
even_list3 = [x for x in num_list3 if x % 2 == 0]
print(even_list3)

リスト内包表記に慣れ親しんだ方であればこっちの方が読みやすいかもしれませんね。

このコードを実行すると、

[実行結果]

[20, 22, 24, 26, 28]

このように同様の機能が実装できたことが確認できます。

リスト内包表記とfilter関数、どちらを使っても大丈夫です。
また、もちろんリスト内包表記の中でも関数を使うことはできます。

ですがリスト内包表記は条件式が複雑になると読みづらくなりますし、forのネストが深くなるとやはり読みづらくでしょう。

更にはリスト内包表記は表現力の強い書き方なので、ここで解説したfilterのみならず、mapなどの機能も再現できてしまいます。

そしてリスト内包表記に慣れ親しんだ人にはそのコードですら読みやすいと感じられることもあるので、他の人にコードを渡すときや、時間が経ってから後で確認する必要がある時などは読みづらさが仇となる場合も考えられます。

その点filterは単一機能ですので(通常mapの代わりにfilterを使うといったことは考えづらいですよね)、読みやすさの観点から見るとこちらの方がおすすめです。

もちろん実行速度などの別の事情もあるので、適宜使いどころを考えてのご使用をおすすめします。

まとめ

この記事ではfilter関数について解説しました。

filter関数の機能はリスト内包表記や、ここで解説しなかったfor文を使った書き方でも再現できます。

ですが読みやすさの観点でこの関数は優秀です。
無駄にコードのインデントを下げたくない時、読みやすさを重視したい時に是非使ってみてくださいね!

この記事を書いた人

第一言語はPythonです。
皆さんRustやりましょう。

目次