note.nkmk.me

Pythonのfilter()でリストから条件を満たす要素を抽出・削除

Posted: 2021-03-28 / Tags: Python, リスト

Pythonのfilter()を使うと、イテラブル(リストやタプルなど)から条件を満たす要素を抽出したり削除したりできる。

ここでは、以下の内容について説明する。

  • filter()の基本的な使い方
    • Python3のfilter()はイテレータを返す
    • リストに変換
    • 条件による要素の抽出・削除
  • lambda(ラムダ式、無名関数)、defで定義した関数を適用
  • 複数条件を適用
  • 第一引数にNoneを指定した場合
  • Falseとなる要素を抽出: itertools.filterfalse()
  • リスト内包表記・ジェネレータ式で代用

なお、最後に述べるように、filter()の処理はリスト内包表記やジェネレータ式で代用可能であり、多くの場合、リスト内包表記・ジェネレータ式を使用するほうが好ましいとされている。

リスト内包表記を使ったリストの要素の抽出などについての詳細は以下の記事を参照。

スポンサーリンク

filter()の基本的な使い方

filter()の第一引数に関数(呼び出し可能オブジェクト)、第二引数にリストなどのイテラブルオブジェクトを指定する。イテラブルの要素を順に関数に適用し、結果がTrueと判定されたものを抽出する。

Python3のfilter()はイテレータを返す

値が偶数(2で割ったときのあまりが0)のときTrueを返すlambda(ラムダ式、無名関数)を例とする。

Python3のfilter()filter型のオブジェクト(イテレータ)を返し、そのままprint()しても中身の値は出力されない。

l = [-2, -1, 0, 1, 2]
print(filter(lambda x: x % 2 == 0, l))
# <filter object at 0x10bb38580>

print(type(filter(lambda x: x % 2 == 0, l)))
# <class 'filter'>

イテレータの値はfor文などで取り出せる。

for i in filter(lambda x: x % 2 == 0, l):
    print(i)
# -2
# 0
# 2

なお、Python2のfilter()はリストを返す。Python2のコードをPython3で実行する場合は注意。

リストに変換

結果をリストに変換したい場合はlist()を使う。

print(list(filter(lambda x: x % 2 == 0, l)))
# [-2, 0, 2]

以降のサンプルコードでは、便宜上、リストに変換した結果を出力する。

条件による要素の抽出・削除

filter()は第一引数の処理の結果がTrueと判定される要素を抽出する。所望の要素を削除したい場合は、条件が反対となるように第一引数を指定すればよい。

例えば、「偶数の要素を削除する」という処理は「奇数の要素を抽出する」という処理と同等。

print(list(filter(lambda x: x % 2 == 0, l)))
# [-2, 0, 2]

print(list(filter(lambda x: x % 2 != 0, l)))
# [-1, 1]

上の例のように、反対の結果となる比較演算子(==!=><=など)に置き換えてもよいし、否定を表すnotを使ってもよい。

'e'で終わる文字列を抽出する例と削除する例は以下の通り。

l_s = ['apple', 'orange', 'strawberry']
print(list(filter(lambda x: x.endswith('e'), l_s)))
# ['apple', 'orange']

print(list(filter(lambda x: not x.endswith('e'), l_s)))
# ['strawberry']

filter()とは反対にFalseとなる要素を残すitertools.filterfalse()という関数もある。条件がややこしい場合はこちらのほうが簡単。後述する。

lambda(ラムダ式、無名関数)、defで定義した関数を適用

filter()の第一引数には呼び出し可能オブジェクトを指定する。これまでの例のように、lambda(ラムダ式、無名関数)を使うことが多いが、defで定義した関数を指定することももちろん可能。

def is_even(x):
    return x % 2 == 0

l = [-2, -1, 0, 1, 2]
print(list(filter(is_even, l)))
# [-2, 0, 2]

複数条件を適用

複数条件を適用したい場合は、そのようなラムダ式や関数を指定すればよい。

and(かつ)やor(または)で複数の条件を繋げられる。

l = [-2, -1, 0, 1, 2]
print(list(filter(lambda x: x % 2 == 0 and x > 0, l)))
# [2]

print(list(filter(lambda x: x % 2 == 0 or x > 0, l)))
# [-2, 0, 1, 2]

第一引数にNoneを指定した場合

filter()の第一引数にNoneを指定した場合、Trueと判定される要素が抽出される。

l_b = [True, False]
print(list(filter(None, l_b)))
# [True]

True, Falseそのものだけでなく、数値やリスト、文字列なども判定される。

例えば、数値の場合は0Falseでそれ以外はTrue、リストや文字列の場合は空だとFalseでそれ以外はTrueと判定される。詳細は以下の記事を参照。

したがって、filter()の第一引数にNoneを指定すると、0や空のリスト、文字列を除去する処理となる。

l = [-2, -1, 0, 1, 2]
print(list(filter(None, l)))
# [-2, -1, 1, 2]

l_2d = [[0, 1, 2], [], [3, 4, 5]]
print(list(filter(None, l_2d)))
# [[0, 1, 2], [3, 4, 5]]

l_s = ['apple', '', 'orange', 'strawberry']
print(list(filter(None, l_s)))
# ['apple', 'orange', 'strawberry']

Falseとなる要素を抽出: itertools.filterfalse()

filter()の反対で、Falseとなる要素を抽出するitertools.filterfalse()も提供されている。

使い方はfilter()と同じ。itertoolsをインポートする必要がある。

import itertools

l = [-2, -1, 0, 1, 2]
print(list(itertools.filterfalse(lambda x: x % 2 == 0, l)))
# [-1, 1]

print(list(itertools.filterfalse(lambda x: x % 2 != 0, l)))
# [-2, 0, 2]

l_s = ['apple', 'orange', 'strawberry']
print(list(itertools.filterfalse(lambda x: x.endswith('e'), l_s)))
# ['strawberry']

上述の通り、filter()でも第一引数の処理を変えれば同じ結果を得られるが、場合によってはfilter()notを使うよりもitertools.filterfalse()のほうが意図が明確なコードが書ける。

itertools.filterfalse()の第一引数をNoneとした場合、Falseと判定される要素が抽出される。あまり使い所はないかもしれない。

l = [-2, -1, 0, 1, 2]
print(list(itertools.filterfalse(None, l)))
# [0]

リスト内包表記・ジェネレータ式で代用

filter()と同等の処理はifを使ったリスト内包表記やジェネレータ式でも実現できる。

filter(function, iterable) は、関数が None でなければジェネレータ式 (item for item in iterable if function(item)) と同等で、関数が None なら (item for item in iterable if item) と同等です。 組み込み関数 - filter() — Python 3.9.2 ドキュメント

l = [-2, -1, 0, 1, 2]
print([x for x in l if x % 2 == 0])
# [-2, 0, 2]

print([x for x in l if x % 2 != 0])
# [-1, 1]

l_s = ['apple', 'orange', 'strawberry']
print([x for x in l_s if x.endswith('e')])
# ['apple', 'orange']

print([x for x in l_s if not x.endswith('e')])
# ['strawberry']
l = [-2, -1, 0, 1, 2]
print([x for x in l if x])
# [-2, -1, 1, 2]

l_2d = [[0, 1, 2], [], [3, 4, 5]]
print([x for x in l_2d if x])
# [[0, 1, 2], [3, 4, 5]]

list(filter())のようにリストを取得したい場合はリスト内包表記、filter()のようにイテレータを取得したい場合はジェネレータ式を使えばよい。

以下のStack Overflowの質問にもあるように、ほとんどの場合、filter()よりもリスト内包表記・ジェネレータ式を使うほうがコードが簡潔・明解で好ましいとされている。

書籍『Effective Python』でもfilter()よりもリスト内包表記を使うべきと書かれている。

処理速度が重要な状況でどちらを使うか迷っている場合、処理速度は様々な要因で変動する可能性もあるので、なるべく想定に近い環境および処理で実際に計測してみることをおすすめする。

スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事