当サイトは、アフィリエイト広告を利用しています

【Python】map, filterの仕組みと使いどころ

作成日:2024月10月22日
更新日:2024年12月18日

Pythonで実装時にリストの編集でよく使うことになる pythonの組み込みの高階関数の

  • filter
  • map

の仕組みと使用方法を調べたので忘備録として残す。

またfilter,mapの仕組みを知る上で重要な概念である

  • 反復可能オブジェクト(iterable)
  • イテレーター(iterator)

についてまとめる

高階関数とは?

関数で

  • 関数を引数としてとることができる
  • 関数を戻り値として返すことができる

の特性を持つものを高階関数と呼ぶ。

  • filter
  • map

は関数を引数としてとるため高階関数に分類される

Pythonの組み込み関数とは?

Pythonの組み込み関数は、標準ライブラリに含まれ、特別なインポートなしで使用できる関数のこと。
高階関数である

  • filter
  • map

はPythonの組み込み関数であるため、特別なインポートなしに使用できる。
※組み込み関数は他にもいろいろある。

反復可能オブジェクト(iterable)とイテレーター(iterator)

反復可能オブジェクトとイテレーターについて
詳しくまとめておく。

反復可能オブジェクトとは?

反復可能オブジェクトは、要素を順番に取り出すことができるもので
具体的な下記が該当する

  • リスト: [1, 2, 3]
  • タプル: (1, 2, 3)
  • 文字列: "abc"
  • 辞書: {"a": 1, "b": 2}
  • 集合: {1, 2, 3}

イテレーターとは?

イテレーターは、反復可能オブジェクトから生成され、要素を一つずつ取り出すためのオブジェクトのこと
一度に一つの要素しか保持できず、全ての要素を反復した後は再利用できなくなる。

特徴としては下記がある

next()メソッド

イテレーターは、next()メソッドを使って次の要素を取得できる
全ての要素を取得すると、StopIteration例外が発生する

next()メソッド
# ディクショナリリスト
users = [
{"user_id": "1", "name": "Tujimura", "age": 11},
{"user_id": "2", "name": "mori", "age": 20},
{"user_id": "3", "name": "shimada", "age": 50},
{"user_id": "4", "name": "kyogoku", "age": 70}
]
# users リストからイテレーターを生成
user_iterator = iter(users)
# next() を使って一つずつユーザーを取得
print(next(user_iterator)) # 出力: {"user_id": "1", "name": "Tujimura", "age": 11}
print(next(user_iterator)) # 出力: {"user_id": "2", "name": "mori", "age": 20}
# 次の要素がないため、StopIteration例外が発生
try:
print(next(user_iterator)) # 50歳のshimadaを取得
print(next(user_iterator)) # 70歳のkyogokuを取得
print(next(user_iterator)) # ここで StopIteration が発生
except StopIteration:
print("すべてのユーザーを取得しました") # 出力: すべてのユーザーを取得しました
# 実行結果
# {'user_id': '1', 'name': 'Tujimura', 'age': 11}
# {'user_id': '2', 'name': 'mori', 'age': 20}
# {'user_id': '3', 'name': 'shimada', 'age': 50}
# {'user_id': '4', 'name': 'kyogoku', 'age': 70}
# すべてのユーザーを取得しました

状態を持つ

イテレーターは、現在の位置(要素のインデックス)を内部的に記憶している
一度反復が完了すると再利用ができない。

状態を持つ
# ディクショナリリスト
users = [
{"user_id": "1", "name": "Tujimura", "age": 11},
{"user_id": "2", "name": "mori", "age": 20},
{"user_id": "3", "name": "shimada", "age": 50},
{"user_id": "4", "name": "kyogoku", "age": 70}
]
# users リストからイテレーターを生成
user_iterator = iter(users)
# 一つずつユーザーを取得(状態が保持される)
print(next(user_iterator)) # 出力: {"user_id": "1", "name": "Tujimura", "age": 11}
print(next(user_iterator)) # 出力: {"user_id": "2", "name": "mori", "age": 20}
# 再度同じイテレーターを使って残りの要素を取得
print(list(user_iterator)) # 出力: [{"user_id": "3", "name": "shimada", "age": 50}, {"user_id": "4", "name": "kyogoku", "age": 70}]
print(list(user_iterator)) # 出力: [] (すでに全ての要素を取得済み)
# 実行結果
# {'user_id': '1', 'name': 'Tujimura', 'age': 11}
# {'user_id': '2', 'name': 'mori', 'age': 20}
# [{'user_id': '3', 'name': 'shimada', 'age': 50}, {'user_id': '4', 'name': 'kyogoku', 'age': 70}]
# []

要は同じ値はとれない。

単方向

イテレーターは単方向にしか進めず、後戻りはできない。

単方向
# ディクショナリリスト
users = [
{"user_id": "1", "name": "Tujimura", "age": 11},
{"user_id": "2", "name": "mori", "age": 20},
{"user_id": "3", "name": "shimada", "age": 50},
{"user_id": "4", "name": "kyogoku", "age": 70}
]
# users リストからイテレーターを生成
user_iterator = iter(users)
# 最初の2ユーザーを取得
print(next(user_iterator)) # 出力: {"user_id": "1", "name": "Tujimura", "age": 11}
print(next(user_iterator)) # 出力: {"user_id": "2", "name": "mori", "age": 20}
# 残りのユーザーを取得(後戻りはできない)
print(next(user_iterator)) # 出力: {"user_id": "3", "name": "shimada", "age": 50}
print(next(user_iterator)) # 出力: {"user_id": "4", "name": "kyogoku", "age": 70}
# 次の要素がないためStopIteration例外が発生
try:
print(next(user_iterator))
except StopIteration:
print("もうユーザーはありません") # 出力: もうユーザーはありません
# 実行結果
# {'user_id': '1', 'name': 'Tujimura', 'age': 11}
# {'user_id': '2', 'name': 'mori', 'age': 20}
# {'user_id': '3', 'name': 'shimada', 'age': 50}
# {'user_id': '4', 'name': 'kyogoku', 'age': 70}
# もうユーザーはありません

遅延評価

イテレーターオブジェクトは遅延評価の仕組みを持っているため
必要になるまで値を評価しない。

必要な時にメモリに読み込み、評価して取り出すようになっている。

イテレーターオブジェクトに対して

  • next
  • list

を使うことでイテレーターオブジェクトを評価して値を取り出すことができる

遅延評価
# ディクショナリリスト
users = [
{"user_id": "1", "name": "Tujimura", "age": 11},
{"user_id": "2", "name": "mori", "age": 20},
{"user_id": "3", "name": "shimada", "age": 50},
{"user_id": "4", "name": "kyogoku", "age": 70}
]
# users リストからイテレーターを生成
user_iterator = iter(users)
# nextで評価して取り出す
print(next(user_iterator)) # 出力: {"user_id": "1", "name": "Tujimura", "age": 11}
print(next(user_iterator)) # 出力: {"user_id": "2", "name": "mori", "age": 20}
# listで評価して取り出す
print(list(user_iterator)) # 出力: [{"user_id": "3", "name": "shimada", "age": 50}, {"user_id": "4", "name": "kyogoku", "age": 70}]

遅延評価を使うことで、メモリ使用量を最小限に抑え、大量のデータを効率的に扱うことができる。

filterの使い方

filterを使ってリストを編集する方法をまとめる。

filter は反復可能オブジェクト(例: リスト)から条件に合う要素を選び出し、
それをイテレーターとして返す

fileter関数の仕組み

filter関数の構文は下記のようになる

filter関数の構文
iterater = filter(function, iterable)
  • 戻り値としてイテレーターオブジェクト(iterater)を返却する
  • 第1引数はフィルタリングするための関数を指定する
  • 第2引数は反復可能オブジェクト(iterable)を指定する

第1関数はbooleanを返す関数である必要がある。
また戻り値はイテレーターオブジェクトであるため、listやnextで評価して値を
取り出してやる必要ががある

filterの実装

filterを使ってリストの編集を実装する

名前付き関数を使う

filter_名前付き関数
# ディクショナリリスト
users = [
{"user_id": "1", "name": "Tujimura", "age": 11},
{"user_id": "2", "name": "mori", "age": 20},
{"user_id": "3", "name": "shimada", "age": 50},
{"user_id": "4", "name": "kyogoku", "age": 70}
]
# 年齢が20以上のユーザーをフィルタリングする関数
def age_above_20(user):
return user["age"] >= 20
# filterを使って条件に一致するユーザーを取得
filtered_users = filter(age_above_20, users)
# 結果をリストに変換して表示
print(list(filtered_users))
# 実行結果
# [
# {'user_id': '2', 'name': 'mori', 'age': 20},
# {'user_id': '3', 'name': 'shimada', 'age': 50},
# {'user_id': '4', 'name': 'kyogoku', 'age': 70}
# ]
  • 名前付き関数を定義してfilterの引数に設定
  • listでイテレーターオブジェクトであるfiltered_usersを評価して出力

無名関数を使う

filter_無名関数
users = [
{"user_id": "1", "name": "Tujimura", "age": 11},
{"user_id": "2", "name": "mori", "age": 20},
{"user_id": "3", "name": "shimada", "age": 50},
{"user_id": "4", "name": "kyogoku", "age": 70}
]
# 無名関数を使って年齢が20以上のユーザーをフィルタリング
filtered_users = filter(lambda user: user["age"] >= 20, users)
# 結果をリストに変換して表示
print(list(filtered_users))
# 実行結果
# [
# {'user_id': '2', 'name': 'mori', 'age': 20},
# {'user_id': '3', 'name': 'shimada', 'age': 50},
# {'user_id': '4', 'name': 'kyogoku', 'age': 70}
# ]
  • filterの第一引数に無名関数を設定
  • listでイテレーターオブジェクトであるfiltered_usersを評価して出力

mapの使い方

mapを使ってリストを編集する方法をまとめる。

mapは反復可能オブジェクト(iterable)の各要素に対して関数を適用し、
その結果をイテレーターとして返す

map関数の仕組み

filter関数の構文は下記のようになる

map関数の構文
iterater = map(function, iterable)
  • 戻り値としてイテレーターオブジェクト(iterater)を返却する
  • 第1引数は各要素に適用する関数を指定する
  • 第2引数は反復可能オブジェクト(iterable)を指定する

戻り値はイテレーターオブジェクトであるため、listやnextで評価して値を
取り出してやる必要ががある

mapの実装

mapを使ってリストの編集を実装する

名前付き関数を使う

map_名前付き関数
users = [
{"user_id": "1", "name": "Tujimura", "age": 11},
{"user_id": "2", "name": "mori", "age": 20},
{"user_id": "3", "name": "shimada", "age": 50},
{"user_id": "4", "name": "kyogoku", "age": 70}
]
# 名前付き関数を使って年齢を1歳増やす処理を定義
def increment_age(user):
return {**user, "age": user["age"] + 1}
# 名前付き関数を map に渡して実行
updated_users_def = map(increment_age, users)
# 結果をリストに変換して表示
print(list(updated_users_def))
# [{'user_id': '1', 'name': 'Tujimura', 'age': 12}, {'user_id': '2', 'name': 'mori', 'age': 21}, {'user_id': '3', 'name': 'shimada', 'age': 51}, {'user_id': '4', 'name': 'kyogoku', 'age': 71}]
  • 名前付き関数を定義してmapの引数に設定
  • listでイテレーターオブジェクトであるupdated_users_defを評価して出力

無名関数を使う

map_無名関数
users = [
{"user_id": "1", "name": "Tujimura", "age": 11},
{"user_id": "2", "name": "mori", "age": 20},
{"user_id": "3", "name": "shimada", "age": 50},
{"user_id": "4", "name": "kyogoku", "age": 70}
]
# lambda を使って年齢を1歳増やす処理を map で行う
updated_users_lambda = map(lambda user: {**user, "age": user["age"] + 1}, users)
# 結果をリストに変換して表示
print(list(updated_users_lambda))
# [{'user_id': '1', 'name': 'Tujimura', 'age': 12}, {'user_id': '2', 'name': 'mori', 'age': 21}, {'user_id': '3', 'name': 'shimada', 'age': 51}, {'user_id': '4', 'name': 'kyogoku', 'age': 71}]
  • mapの第一引数に無名関数を設定
  • listでイテレーターオブジェクトであるupdated_users_lambdaを評価して出力

リスト内包表記との違いは?

リスト内包表記を使えば、上記のfilterやmapと同じことを実装することができるので
二つの違いをまとめておく。

リスト内包表記に関しては下記記事でまとめています

二つの違い

  • filterやmapはイテレーターオブジェクトを返すため、結果がすぐにリストに変換されず、必要な時に評価する。このため大量のデータを扱う際にメモリ効率が向上する
  • filterやmapは関数型プログラミングスタイルを採用できる
  • リスト内包表記はすぐにすべての結果を計算し、メモリにリストとして格納するため、大量のデータを処理する場合はメモリ使用量が増える。
  • リスト内包表記は、通常のforループやfilterよりも高速に処理できる
  • リスト内包表記は直感的で理解しやすい

使い分け

メモリ効率が重要で、大量のデータを処理する場合は、メモリ使用量を抑えられるのでmapやfilterを使う。 一方、小規模データや、すぐに結果が必要な場合は、リスト内包表記の方を使う。

まとめ

pythonでのmapやfilterの使い方を仕組みから調べて
まとめてみた。

今まではリスト内包表記やジェネレータ式をメインで書いていたので
今後は状況に応じて、filterやmapも使用していきたい

参考

関連記事

新着記事

タグ別一覧
top