ユーニックス総合研究所

  • home
  • archives
  • python-filter

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]のうち13Trueになります。
よって抽出される要素は[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 objectfilterが呼ばれた後に返されますが、この段階ではフィルタリングは行われていません。
実際にフィルタリングが行われるのはlisttuple, 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.cfilter_new()です。

filter_new() · python/cpython · GitHub

この関数ではfilterobjectという構造体を作成し、そこに初期化を行っています。
引数の関数をfuncというメンバ変数に保存し、引数のシーケンシャルオブジェクトからイテレーターを取得してitというメンバ変数に格納しています。

このfilterobjectは走査されるときにfilter_next()を呼び出します。

filter_next() · python/cpython · GitHub

この関数ではitから要素を取り出して、それをfunc()で評価しています。
評価が真ならfilter_next()から要素を返し、評価が偽ならNULLを返しています。

filter_next()ではfuncNoneboolの場合は走査する要素が真かどうかだけのチェックを行い、要素を返しています。
真かどうかチェックするために使っている関数は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引数として適当なものを答えよ

  1. ラムダ式
  2. 関数
  3. リスト

Q2: filterの第2引数として適当なものを答えよ

  1. 辞書
  2. リスト
  3. タプル

Q3: filterの第1引数をNoneにした場合の挙動を答えよ

  1. 真の要素を結果に格納する
  2. 偽の要素を結果に格納する
  3. すべての要素を結果に格納する

正解

Q1: 1, 2
Q2: 1, 2, 3
Q3: 1