高階関数と Lambda

Python では、関数も「値」です——変数に代入したり、他の関数に渡したりすることが、数値や文字列と同じようにできます。これにより非常に簡潔でエレガントなコードが可能になります。高階関数は、他の関数を引数として受け取ったり、関数を返したりする関数です。このレッスンはあなたに新しい世界を開くでしょう。


1. 関数は第一級オブジェクト

Python では、関数は他の値と同じように操作できます:

例:関数を値として扱う

PYTHON
def say_hello(name):
    return f"Hello, {name}"

# 関数を変数に代入
my_func = say_hello
print(my_func("Alice"))      # Hello, Alice

# 関数を引数として渡す
def greet_with(func, name):
    return func(name)

print(greet_with(say_hello, "Bob"))   # Hello, Bob

# 関数から関数を返す
def create_greeter(greeting):
    def greeter(name):
        return f"{greeting}, {name}!"
    return greeter

say_hi = create_greeter("Hi")
say_hello = create_greeter("Hello")

print(say_hi("Alice"))       # Hi, Alice!
print(say_hello("Bob"))      # Hello, Bob!
▶ 試してみよう
💡 「関数が第一級オブジェクトである」とはどういう意味ですか? 関数が整数、文字列、リストと同じレベルにあることを意味します——変数に代入したり、リストに入れたり、引数として渡したり、値として返したりできます。このプログラミングパラダイムを「関数型プログラミング」と呼びます。


2. map():一括変換

map() はシーケンス内のすべての要素に関数を適用し、新しいイテレータを返します:

例:map による一括変換

PYTHON
# リスト内の各数値を2乗
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x ** 2, numbers))
print(squared)              # [1, 4, 9, 16, 25]

# 文字列リストを大文字に変換
fruits = ["apple", "banana", "cherry"]
upper_fruits = list(map(str.upper, fruits))
print(upper_fruits)         # ['APPLE', 'BANANA', 'CHERRY']

# 摂氏から華氏へ
temps_c = [0, 10, 20, 30, 40]
temps_f = list(map(lambda c: c * 9 / 5 + 32, temps_c))
print(temps_f)              # [32.0, 50.0, 68.0, 86.0, 104.0]
▶ 試してみよう

map() はリスト内包表記と同等: map(f, seq)[f(x) for x in seq]。どちらを使うかは好みの問題です——内包表記の方がより「Python らしい」です。


3. filter():一括フィルタリング

filter() は関数が True を返す要素を選択します:

例:filter によるデータ抽出

PYTHON
# 偶数のみ抽出
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens)                # [2, 4, 6, 8, 10]

# 長さ 5 以上の単語を抽出
words = ["cat", "elephant", "dog", "giraffe", "ant"]
long_words = list(filter(lambda w: len(w) >= 5, words))
print(long_words)           # ['elephant', 'giraffe']

# 空でない文字列を抽出
data = ["hello", "", "world", "", "python"]
non_empty = list(filter(None, data))   # None は「falsy な値を除去」を意味
print(non_empty)            # ['hello', 'world', 'python']
▶ 試してみよう

filter() も内包表記と同等: filter(f, seq)[x for x in seq if f(x)]。関数として None を使うと、すべての falsy な値(""0NoneFalse など)を除去することと同等です。


4. 高度な sorted() の使い方

レッスン 11 では sorted()key パラメータを学びました。さらに深く掘り下げましょう:

例:sorted を使ったカスタムソート

PYTHON
# オブジェクト属性でソート
students = [
    {"name": "Alice", "score": 85},
    {"name": "Bob", "score": 92},
    {"name": "Charlie", "score": 78},
]

# スコア降順でソート
ranked = sorted(students, key=lambda s: s["score"], reverse=True)
for s in ranked:
    print(f"{s['name']}: {s['score']}")

# 名前の長さでソート
sorted_by_name = sorted(students, key=lambda s: len(s["name"]))
print([s["name"] for s in sorted_by_name])   # ['Alice', 'Bob', 'Charlie']
▶ 試してみよう

例:マルチレベルソート(難易度 ⭐⭐)

PYTHON
# スコア降順、次に名前のアルファベット順でソート
data = [
    ("Alice", 85),
    ("Bob", 92),
    ("Charlie", 85),
    ("Diana", 92),
]

def sort_key(item):
    # タプルで返す:まずスコア降順(負の数で反転)、次に名前
    return (-item[1], item[0])

sorted_data = sorted(data, key=sort_key)
for name, score in sorted_data:
    print(f"{name}: {score}")
▶ 試してみよう

出力:

TEXT
Bob: 92
Diana: 92
Alice: 85
Charlie: 85

5. reduce():累積計算

reduce() はシーケンス内の要素を累積し、一度に 2 つの要素に関数を適用します。組み込み関数ではなく、functools からインポートします:

例:reduce による累積計算

PYTHON
from functools import reduce

# すべての数値の積
numbers = [1, 2, 3, 4, 5]
product = reduce(lambda x, y: x * y, numbers)
print(product)              # 120 (1×2×3×4×5)

# 最大値を求める
max_val = reduce(lambda a, b: a if a > b else b, [3, 1, 4, 1, 5])
print(max_val)              # 5

# 文字列を連結
words = ["Hello", " ", "World", " ", "Python"]
sentence = reduce(lambda a, b: a + b, words)
print(sentence)             # Hello World Python
▶ 試してみよう

reduce() の実行過程

PYTHON
# reduce(lambda x, y: x + y, [1, 2, 3, 4])
# ステップ 1:x=1, y=2 → 3
# ステップ 2:x=3, y=3 → 6
# ステップ 3:x=6, y=4 → 10
# 結果:10
💡 reduce() vs ループ: reduce() でできることは for ループでもできます。reduce() はより簡潔で宣言的です——「どうやるか」ではなく「何をするか」を記述します。欠点は、初心者には理解が難しいことです。


6. Lambda 式の深掘り

Lambda はギリシャ文字の λ にちなんで名付けられ、本質的には無名関数——名前のない小さな関数で、使ったら捨てられます。

Lambda と通常関数の比較

例:Lambda vs 通常関数

PYTHON
# 通常の関数
def double(x):
    return x * 2

# Lambda 式
double = lambda x: x * 2

print(double(5))            # 10
▶ 試してみよう

完全な Lambda 構文

PYTHON
lambda parameters: expression
# ↑ 以下と同等
def anonymous_function(parameters):
    return expression

複数パラメータ

例:複数パラメータの Lambda

PYTHON
add = lambda a, b: a + b
print(add(3, 5))            # 8

# Lambda 内での三項演算子
max_val = lambda a, b: a if a > b else b
print(max_val(10, 20))      # 20
▶ 試してみよう

Lambda を使うタイミング

例:Lambda のユースケース

PYTHON
# ✅ 良い使い方:高階関数の引数としてのシンプルな 1 行変換
numbers = [1, 2, 3, 4]
result = list(map(lambda x: x ** 2, numbers))

# ❌ 悪い使い方:複数行必要な複雑なロジック — 通常の def 関数を使用
▶ 試してみよう
💡 Lambda の経験則: ロジックが複数の式や if-elif 分岐を必要とするなら、lambda を使わず通常の関数を書きましょう。Lambda のエレガントさはその「ミニマリズム」にあります。


よくあるユースケース


❓ よくある質問

Q map() とリスト内包表記、どちらが優れていますか?
A Python コミュニティはリスト内包表記を好みます——より直感的で、より Python らしいです。map() の利点:① 既存の関数名がある場合に短い(map(str.upper, seq) vs [s.upper() for s in seq])。② 遅延イテレータを返すため、大規模データでメモリを節約。ほとんどの場合、リスト内包表記が良い選択です。
⚠️ Q:lambda は複数行にわたれますか? A:いいえ。Lambda の構文は単一の式に制限されています。複数行のロジックには通常の関数を定義してください。Python の lambda は他の言語(JavaScript など)より意図的に弱く設計されています——Guido(Python の生みの親)は「明示的は暗黙的よりも優れている」と信じています。

Q reduce() は現代の Python でも使われていますか?
A あまり使われていません。Guido 自身が reduce() を好んでおらず、理解が難しいと考えていました。ほとんどの reduce() のシナリオはループやより直接的な関数(sum()max() など)で置き換えられます。ただし、reduce() は関数型プログラミングにおける重要な概念です。

📖 まとめ


📝 練習問題

  1. 基本(難易度 ⭐)nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] として、map()filter() を使って:奇数のみを抽出し、その平方を計算してください。ヒント:最初に filter で奇数を抽出、次に map で平方を計算。

  2. 中級(難易度 ⭐⭐):辞書のリスト products = [{"name": "A", "price": 100}, {"name": "B", "price": 80}, {"name": "C", "price": 120}] が与えられたとき、sorted() を使って価格の降順でソートし、名前と価格を出力してください。

  3. 挑戦(難易度 ⭐⭐⭐)reduce() を使って flatten(nested_list) 関数を実装してください。ネストしたリストを 1 レベル平坦化します。例:flatten([[1, 2], [3, 4], [5]])[1, 2, 3, 4, 5] を返します。ヒントreduce で毎回 + を使って 2 つのリストを結合。次に同じものをリスト内包表記で実装し、アプローチを比較してください。

100%