Python WebAcademy Blog

Pythonのmap()とfilter()とは?リスト内包表記との使い分けと、一度しか使えない落とし穴を初心者向けに解説

|

リストの全要素に同じ処理をしたい、条件に合うものだけ取り出したい。そんなときに使えるのが組み込み関数のmap()とfilter()です。基本の書き方からlambdaとの組み合わせ、リスト内包表記との使い分け、結果が一度しか使えないイテレータの落とし穴、Python 3.14で追加されたstrict引数まで、手元で動かした結果とともに初心者向けに解説します。

入力された数字を、文字列から整数にまとめて変換したい。そんな場面で、forループと空のリストを用意して1つずつappendしていませんか。

もちろんそれでも正しく動きます。ただ、ほかの人のコードを読んでいると、map()やfilter()という見慣れない関数に出会うことがあるはずです。

map()は全要素に同じ処理をする関数で、filter()は条件に合う要素だけを残す関数です。どちらもimportなしで使える組み込み関数なので、知っておくとコードを読む力がぐっと上がります。

今回は、2つの関数の基本から、リスト内包表記との使い分け、つまずきやすい落とし穴まで紹介します。手元のPython 3.11と3.14で動かした結果を載せているので、一緒に確かめながら読んでみてください。

map()とfilter()は何をする関数か

まずは、2つの関数の役割をざっくり押さえておきましょう。どちらも、リストのようにたくさんの値をまとめて扱うときに使います。

map()は、渡した関数をすべての要素に順番に当てはめる関数です。たとえば、全部の数字を2倍にする、全部の文字列を大文字にする、といった処理に向いています。

filter()は、渡した関数がTrueを返した要素だけを残す関数です。偶数だけを取り出す、空の文字列を取り除く、といった絞り込みに使います。

2つの違いを、表で並べてみました。

関数 役割 要素の数 渡す関数が返すもの
map() 全要素を変換する 元と同じ 変換後の値
filter() 条件に合う要素だけ残す 元と同じか少ない TrueかFalse

map()は数が変わらず中身が変わり、filter()は中身が変わらず数が減る、と覚えるとわかりやすいですね。この違いさえつかめば、どちらを使えばよいか迷うことは少なくなります。

map()の基本的な使い方

ここからは、実際にコードを動かしながら見ていきましょう。最初は、初心者がいちばん出会いやすい文字列から整数への変換です。

文字列のリストを整数に変換する

input()で受け取った値は、すべて文字列になります。スペース区切りの数字をまとめて整数にしたいときは、次のように書けます。

text = "10 20 30"

numbers = list(map(int, text.split()))
print(numbers)  # [10, 20, 30]

map()の1つ目の引数には関数、2つ目にはリストなどを渡します。ここではint関数を、split()で分けた3つの文字列それぞれに当てはめています。

ポイントは、intのうしろにかっこを付けないことです。int()と書くとその場で呼び出してしまうので、関数そのものを渡すつもりで名前だけを書きます。

同じ処理をforループで書くと、次のようになります。

text = "10 20 30"

numbers = []
for s in text.split():
    numbers.append(int(s))
print(numbers)  # [10, 20, 30]

4行かかっていた処理が、map()なら1行で済みましたね。競技プログラミングの入力処理でよく見かけるのも、この書き方です。

lambdaと組み合わせて自分の処理を当てはめる

int()やstr()のような既存の関数がないときは、lambdaで小さな関数をその場で作って渡せます。全部の値を2倍にする例を見てみましょう。

prices = [100, 250, 480]

doubled = list(map(lambda x: x * 2, prices))
print(doubled)  # [200, 500, 960]

lambda x: x * 2は、xを受け取ってx * 2を返す関数という意味です。名前を付けるほどでもない使い捨ての処理に向いています。

lambdaの書き方にまだ自信がない方は、先にこちらで基本を確認しておくと読みやすくなります。【関連記事】ラムダ式(無名関数)を使いこなす。コードを極限までシンプルにする方法を解説

filter()の基本的な使い方

続いてfilter()です。こちらは、条件に合う要素だけを取り出したいときに使います。

偶数だけを残す例を書いてみました。

numbers = [1, 2, 3, 4, 5, 6]

evens = list(filter(lambda n: n % 2 == 0, numbers))
print(evens)  # [2, 4, 6]

lambdaの部分が、残すかどうかを決める判定役です。nを2で割った余りが0ならTrueになるので、偶数だけが結果に残ります。

Noneを渡すと偽の値をまとめて取り除ける

filter()には、ちょっと便利な使い方があります。1つ目の引数に関数ではなくNoneを渡すと、偽とみなされる値がまとめて取り除かれるのです。

values = [0, 1, "", None, "a", []]

print(list(filter(None, values)))  # [1, 'a']

0や空の文字列、None、空のリストが消えて、1と"a"だけが残りました。CSVを読み込んだあとに空のセルを捨てたいときなどに、さっと使えて重宝します。

ただし、0も一緒に消えてしまう点には注意してください。数値の0を正しいデータとして残したい場面では、filter(lambda v: v is not None, values)のように条件をはっきり書くほうが安全です。

結果が一度しか使えないイテレータの落とし穴

ここからが、今回いちばん伝えたい内容です。map()やfilter()の結果をprint()でそのまま表示すると、思っていたのと違うものが出てきます。

m = map(str, [1, 2, 3])

print(m)        # <map object at 0x7f...>
print(list(m))  # ['1', '2', '3']
print(list(m))  # []

1行目の表示は、リストではなくmapオブジェクトというものです。Python 3のmap()とfilter()は、リストではなくイテレータを返すようになっています。

イテレータは、値を1つずつ取り出すための仕組みです。取り出し終わったら空っぽになるので、3行目のように2回目のlist()では空のリストが返ってきます。

実務でハマった集計が0件になる不具合

私は10年ほどエンジニアとして開発に関わってきましたが、このイテレータの性質で半日ほど悩んだことがあります。filter()で絞り込んだ結果を、件数のチェックと集計の2か所で使っていたところ、集計のほうだけ毎回0件になっていました。

原因は、件数のチェックでlist()に変換した時点で、filterオブジェクトの中身を使い切っていたことでした。エラーも出ずに静かに0件になるので、なかなか気づけなかったのです。

それ以来、2回以上使う結果は、最初にlist()でリストにしてから変数に入れるようにしています。書き方は次のとおりです。

orders = [1200, 0, 3500, 800]

valid = list(filter(lambda x: x > 0, orders))
print(len(valid))  # 3
print(sum(valid))  # 5500

逆に言えば、1回だけ使うならイテレータのままでも問題ありません。必要になったときに1件ずつ計算するので、大きなデータでもメモリを節約できるのが長所です。

値を1つずつ作る仕組みはジェネレータと同じ考え方なので、こちらの記事も参考になります。【関連記事】ジェネレータ(yield)っていつ使うの?巨大なデータをメモリ節約して扱う方法

リスト内包表記とどちらを使うべきか

ここまで読んで、それってリスト内包表記でも書けるのでは?と思った方もいるかもしれません。実はそのとおりで、map()とfilter()はどちらも内包表記で書き換えられます。

同じ処理を並べて比べてみましょう。

numbers = [1, 2, 3, 4, 5, 6]

# map()とfilter()を組み合わせた書き方
a = list(map(lambda n: n * 10, filter(lambda n: n % 2 == 0, numbers)))

# リスト内包表記の書き方
b = [n * 10 for n in numbers if n % 2 == 0]

print(a)  # [20, 40, 60]
print(b)  # [20, 40, 60]

結果は同じですが、読みやすさはずいぶん違いますね。lambdaが2つ重なる書き方より、内包表記のほうが左から右へ素直に読めます。

Python 3.0のリリース時の公式ドキュメントでも、map()の結果をlist()で包む代わりに、内包表記を使うかコードを見直すことが勧められています。Pythonの世界では、lambdaが必要になる場面は内包表記を選ぶのが一般的です。

使い分けの目安を、表にまとめてみました。

場面 おすすめ 理由
既存の関数をそのまま当てはめる map() map(int, items)のように短く書ける
lambdaを書く必要がある 内包表記 式をそのまま書けて読みやすい
変換と絞り込みを両方したい 内包表記 forとifを1行にまとめられる
偽の値をまとめて捨てたい filter(None, ...) 意図が短く伝わる
結果を1回だけ順番に使う map()かジェネレータ式 リストを作らずメモリを節約できる

迷ったら内包表記を選び、int()やstr.strip()のような既存の関数を当てはめるだけのときにmap()を使う。このくらいの感覚で十分です。

内包表記の書き方をくわしく知りたい方は、こちらの記事でじっくり解説しています。【関連記事】Pythonのリスト内包表記を使いこなせ!3行のループを1行にまとめる書き方

複数のリストを渡すmap()とstrict引数

map()には、リストを2つ以上渡せるという特徴もあります。このとき、関数には各リストから1つずつ取り出した値が同時に渡されます。

prices = [100, 200, 300]
counts = [2, 5, 1]

totals = list(map(lambda p, c: p * c, prices, counts))
print(totals)  # [200, 1000, 300]

単価と個数のリストから、それぞれの小計を一気に計算できました。zip()で組にしてから計算するのと同じことを、1回で済ませている形です。

ここで気をつけたいのが、リストの長さがそろっていないときの動きです。map()は短いほうのリストが終わった時点で止まり、残りの要素は黙って無視されます。

Python 3.14で追加されたstrict引数

この黙って無視される動きは、データの取りこぼしに気づきにくい原因になります。そこでPython 3.14では、map()にstrictというキーワード引数が追加されました。

strict=Trueを付けると、長さがそろっていないときにValueErrorが出るようになります。手元のPython 3.14で試した結果は次のとおりです。

a = [1, 2, 3]
b = [10, 20]

print(list(map(lambda x, y: x + y, a, b)))  # [11, 22]
print(list(map(lambda x, y: x + y, a, b, strict=True)))
# ValueError: map() argument 2 is shorter than argument 1

同じ仕組みは、Python 3.10からzip()にも用意されています。長さがそろっているはずのデータを組み合わせるときは、strict=Trueを付けておくと安心です。

ただし、Python 3.13以前ではstrict引数は使えずエラーになります。チームで使っているPythonのバージョンを確認してから取り入れてください。

zip()の基本的な使い方は、こちらの記事で紹介しています。【関連記事】Pythonのzip関数の使い方を解説!複数のリストをスマートにまとめる技

reduce()がfunctoolsにある理由

map()とfilter()を調べていると、reduce()という関数の名前もよく一緒に出てきます。reduce()は、要素を左から順に1つの値へたたみ込んでいく関数です。

Python 2では組み込み関数でしたが、Python 3.0からはfunctoolsモジュールへ移されました。使うときは、次のようにimportが必要です。

from functools import reduce

numbers = [1, 2, 3, 4]

product = reduce(lambda a, b: a * b, numbers)
print(product)  # 24

1×2×3×4を計算して、24が返ってきました。とはいえ、合計ならsum()、最大値ならmax()のように、専用の関数があるときはそちらを使うほうが読みやすくなります。

functoolsには、ほかにもキャッシュを効かせるlru_cacheなど便利な道具がそろっています。興味のある方は、こちらもあわせて読んでみてください。【関連記事】Pythonのfunctoolsとは?lru_cacheやpartialで関数を速く短くする使い方を初心者向けに解説

まとめ

map()は全要素に同じ処理を当てはめる関数で、filter()は条件に合う要素だけを残す関数です。どちらも組み込み関数なので、importなしですぐに使えます。

結果はリストではなくイテレータで返ってくるので、一度使うと空っぽになります。2回以上使うなら、最初にlist()でリストにしておくのが鉄則です。

lambdaを書く必要があるときは、リスト内包表記のほうが読みやすくなることが多いです。map(int, items)のように既存の関数を当てはめる場面では、map()の短さが光ります。

まずは、手元のコードにある文字列を整数に変換するループを、1か所だけmap()に書き換えてみてはいかがでしょうか。読み返したときに、すっきりしたと感じられるはずです。

ここまでお読みいただきありがとうございました。

参考情報

次のアクション

記事で学んだ内容を実際に動かしてみよう

Python WebAcademyでは、ブラウザ上でコードを書きながら基礎から実践まで体系的に学べます。

Python WebAcademyの学習画面

あわせて読む

関連記事

ブログ一覧へ

Python学習ロードマップ

まずはこの3講座から

記事で気になったテーマを、順番に手を動かしながら学べます。

ロードマップを見る