Pythonのfilter関数でリストなどの要素を抽出・削除
- 作成日: 2020-09-05
- 更新日: 2024-01-02
- カテゴリ: Python
Pythonのfilterの使い方
Pythonには組み込み関数にfilter
があります。
filter
を使うとリストなどをフィルタリングすることが可能です。
↓のように使います。
lis = [1, 2, 3]
is_even = lambda el: el % 2 == 0
result = filter(is_even, lis)
result = list(result)
print(result)
出力結果。
[2]
filterの構造
filter
は↓のような構造を持っています。
filter(関数など, イテレート可能なオブジェクト)
戻り値はfilter object
です。これはリストなどに変換可能です。
引数の「関数など」にはラムダ式などの関数やNone
などを指定します。
引数の「イテレート可能なオブジェクト」にはリストなどのイテレート可能なオブジェクトを渡します。
filter
は引数の関数が指定されている場合は、その関数を使ってイテレート可能なオブジェクトをフィルタリングします。
要素の参照ごとに関数を呼び出し、引数にその要素を渡します。この関数がTrue
を返せば戻り値のオブジェクトにその要素を格納し、関数がFalse
を返せば戻り値のオブジェクトにその要素を格納しません。
filter
は第1引数がNone
の場合は、真の要素のみを戻り値に格納します。
filter()の第1引数に渡せるオブジェクト
filter()
の第1引数に渡す関数などは真偽値(整数や文字列も可)を返すことを求められます。
関数がTrue
なら走査中の要素を格納し、False
なら要素を格納しません。
また関数などの引数にはfilter()
の第2引数のリストなどの要素が先頭から渡されていきます。
None
の場合は要素が真の場合に要素を抽出します。
lambda式を第1引数にする
filter()
の第1引数にはlambda
式を指定できます。
lambda
式がTrue
を返す場合は要素を格納し、False
の場合は要素を格納しません。
fltr = filter(lambda _: True, [1, 2, 3])
print(list(fltr))
fltr = filter(lambda _: False, [1, 2, 3])
print(list(fltr))
出力結果。
[1, 2, 3]
[]
lambda
式の第1引数には走査中の要素が渡されます。これは第2引数のリストなどの要素です。
この要素から真偽値を求めることで、その要素を結果に格納するかどうかの式を書くことが出来ます。
↓は要素(整数)が偶数ならTrue
を返す式です。
fltr = filter(lambda el: el % 2 == 0, [1, 2, 3])
print(list(fltr))
出力結果。
[2]
defで定義した関数を第1引数にする
filter()
の第1引数にはdef
で定義した関数も渡せます。
def func(x):
return x % 2 == 0
print(list(filter(func, [0, 1, 2, 3])))
# [0, 2]
この場合は関数func()
の引数x
がリストの要素になります。
上記のコードではx % 2 == 0
という式でx
が偶数だったらTrue
を返すようにしています。
filter()
に渡しているリストには[0, 1, 2, 3]
という要素が入っていますので、抽出されるのは[0, 2]
になります。
Noneを第1引数にする
filter()
の第1引数にはNone
も渡すことができます。
None
を渡した場合、検証する要素が真(True
)の場合に要素を抽出します。
print(list(filter(None, [0, 1, False, 3])))
# [1, 3]
上記の場合では[0, 1, False, 3]
のうち1
と3
がTrue
になります。
よって抽出される要素は[1, 3]
になります。
この動作は以下のlambda
式を使った場合と同様です。
print(list(filter(lambda x: x, [0, 1, False, 3])))
# [1, 3]
boolを第1引数にする
filter()
の第1引数にはbool
も渡せます。
この場合の動作はNone
を渡した場合と同様になります。
print(list(filter(bool, [0, 1, False, 3])))
# [1, 3]
filterの戻り値
filter
の戻り値はfilter object
です。
これはリストやタプルに変換することが出来ます。
class filter(object)
filter(function or None, iterable) --> filter object
filter object
をリストに変換する場合はオブジェクトをlist
に渡し、タプルに変換する場合はtuple
に渡します。
リストに変換する場合↓。
lis = [1, 2, 3]
iseven = lambda el: el % 2 == 0
fltr = filter(iseven, lis)
print(type(fltr))
lis = list(fltr)
print(type(lis))
print(lis)
出力結果。
<class 'filter'>
<class 'list'>
[2]
タプルに変換する場合。
lis = [1, 2, 3]
iseven = lambda el: el % 2 == 0
fltr = filter(iseven, lis)
print(type(fltr))
tpl = tuple(fltr)
print(type(tpl))
出力結果。
<class 'filter'>
<class 'tuple'>
(2,)
ちなみにfilter
オブジェクトを連続してlist()
やtuple()
に渡しても期待した結果は得られません。
これは、最初の変換でfilter
オブジェクトがStopIteration
まで進んでしまうためです。
lis = [1, 2, 3, 4]
fltr = filter(lambda x: x % 2, lis)
print(list(fltr)) # 1回目の変換はOK
print(tuple(fltr)) # 連続した2回目の変換は・・・・・・
出力結果。
[1, 3]
()
filterオブジェクトの評価タイミング
filter object
はfilter
が呼ばれた後に返されますが、この段階ではフィルタリングは行われていません。
実際にフィルタリングが行われるのはlist
やtuple
, next
などにfilter object
を渡したときです。
fltr = filter(None, [1, 2, 3])
print(next(fltr))
print(next(fltr))
print(next(fltr))
出力結果。
1
2
3
この設計は大量の要素を持つオブジェクトをフィルタリングしたい時に有効です。
つまりリスト内包表記などでは文が終了した段階ですべての要素がリストに格納されるため、巨大なrange
などを処理しようとすると処理がとても遅くなります。
# 環境によっては遅い
lis = [el for el in range(100000000)]
print(lis[0])
しかしfilter
は実際のフィルタリングのタイミングをユーザー側で制御できます。
そのため↓のような巨大なrange
に対しても短時間で戻り値を取得することが出来ます。
# すぐおわる
fltr = filter(None, range(100000000))
print(next(fltr))
filter()のCPythonでの実装
GitHub - python/cpython: The Python programming language
CPythonはPythonのC言語による実装です。
filter()
のCPythonでの実装ですが、filter()
を呼び出したときに呼ばれる関数はcpython/Python/bltinmodule.c
のfilter_new()
です。
filter_new() · python/cpython · GitHub
この関数ではfilterobject
という構造体を作成し、そこに初期化を行っています。
引数の関数をfunc
というメンバ変数に保存し、引数のシーケンシャルオブジェクトからイテレーターを取得してit
というメンバ変数に格納しています。
このfilterobject
は走査されるときにfilter_next()
を呼び出します。
filter_next() · python/cpython · GitHub
この関数ではit
から要素を取り出して、それをfunc()
で評価しています。
評価が真ならfilter_next()
から要素を返し、評価が偽ならNULL
を返しています。
filter_next()
ではfunc
がNone
かbool
の場合は走査する要素が真かどうかだけのチェックを行い、要素を返しています。
真かどうかチェックするために使っている関数はPyObject_IsTrue()
になります。
PyObject_IsTrue() · python/cpython · GitHub
それ以外の場合はfunc
を関数として呼び出し、評価しています。
filter()の使用例
filter
の使用例です。
特定の文字列をフィルタリングする
名前が格納されたリストから特定の頭文字の名前のみを抽出します。
def is_valid_name(name):
return name[0].lower() in ['c', 'd']
lis = ['cat', 'dog', 'bird', 'pig']
fltr = filter(is_valid_name, lis)
print(list(fltr))
# ['cat', 'dog']
偶数と奇数をフィルタリングする
偶数のみを抽出する場合は以下のコードになります。
lis = [1, 2, 3, 4]
f = filter(lambda x: x % 2 == 0, lis)
print(list(f))
# [2, 4]
奇数のみを抽出する場合は以下のコードになります。
lis = [1, 2, 3, 4]
f = filter(lambda x: x % 2 == 1, lis)
print(list(f))
# [1, 3]
問題
Q1: filter
の第1引数として適当なものを答えよ
- ラムダ式
- 関数
- リスト
Q2: filter
の第2引数として適当なものを答えよ
- 辞書
- リスト
- タプル
Q3: filter
の第1引数をNone
にした場合の挙動を答えよ
- 真の要素を結果に格納する
- 偽の要素を結果に格納する
- すべての要素を結果に格納する
正解
Q1: 1, 2
Q2: 1, 2, 3
Q3: 1