コンテンツにスキップ

第5回:高階関数とラムダ式

先週、こんなコード書きましたよね?

第4回の応用課題で書いた my_summy_maxmy_min を並べてみると...

def my_sum(data, n):
    s = 0
    for i in range(n):
        s = s + data[i]   # ← 足す
    return s

def my_max(data, n):
    m = data[0]
    for i in range(n):
        if data[i] > m:   # ← 大きい方を残す
            m = data[i]
    return m

def my_min(data, n):
    m = data[0]
    for i in range(n):
        if data[i] < m:   # ← 小さい方を残す
            m = data[i]
    return m

for ループの外側はほぼ同じ。違うのは「中で何をするか」だけ。 「コピペして1箇所だけ変える」作業、面倒ですよね?

もし「処理を引数にできたら」?

ループの枠組みを1個に固定して、中身の処理だけ差し替えられたら...?

# こんな関数があったら?
def 処理する(data, やること):
    結果 = []
    for x in data:
        結果.append(やること(x))   # 「やること」を関数として受け取る
    return 結果

# 1個の関数で、いろんな処理ができる
処理する([1, 2, 3], 2倍にする)      # → [2, 4, 6]
処理する([1, 2, 3], 3を足す)        # → [4, 5, 6]
処理する([1, 2, 3], 文字列にする)   # → ['1', '2', '3']

これが高階関数の発想。

高階関数

高階関数: 関数を引数で受け取る関数。

def print_price(price, func):
    print('価格は ' + func(price))

def price_without_tax(price):
    return f'税抜き{price}円'

def price_with_tax(price):
    return f'税込み{int(price * 1.1)}円'

print_price(800, price_without_tax)   # 価格は 税抜き800円
print_price(800, price_with_tax)      # 価格は 税込み880円

ポイント:

  • print_price は引数 func関数を受け取る
  • 関数を呼び出すときは print_price(800, price_without_tax) のように、カッコをつけずに関数名だけ渡す
  • 関数の中で func(price) のように使うと、受け取った関数を呼び出せる

身近な高階関数

  • Excelの並び替え: 「降順」「昇順」など、並び替え方を引数で渡している
  • SNSの並び替え: 「新着順」「人気順」「いいね数順」など、基準を引数で切り替えている
  • スマホの写真フィルタ: どのフィルタにするかを引数で渡している

「処理の中身を引数で渡す」発想は、毎日触る機能の裏側で動いている基本パターン。

プレ演習5-1

以下のコードをそのまま打って実行してみよう。

def double(x):
    return x * 2

def apply_one(func, value):
    return func(value)

print(apply_one(double, 5))
print(apply_one(double, 10))

期待される実行結果:

10
20

プレ演習5-2

プレ演習5-1のコードに、3を足す関数 add_three を追加して、apply_one(add_three, 5) を呼んでみよう。

print(apply_one(double, 5))
print(apply_one(add_three, 5))

期待される実行結果:

10
8

プレ演習5-3

リストの各要素に関数を適用する高階関数 apply_list を完成させよう。

def double(x):
    return x * 2

def apply_list(func, items):
    result = []
    for x in items:
        result.append( ____ )   # ← ここを埋める
    return result

print(apply_list(double, [1, 2, 3, 4, 5]))

期待される実行結果:

[2, 4, 6, 8, 10]

余裕のある人向け: 「2乗する関数 square」も作って実行してみよう。


def 書くの、ちょっと大げさじゃない?

高階関数は便利。でも...

def double(x):
    return x * 2

def add_three(x):
    return x + 3

def square(x):
    return x * x

たった1行の処理のために、毎回 def で名前をつけて3行書くのは大げさ。 しかも double, add_three, square を1回しか使わないなら、名前をつけるのも無駄。

→ 「使い捨ての小さい関数」を、もっと簡潔に書きたい!

それを叶えるのが、ラムダ式(lambda)

ラムダ式(lambda 式)

関数の表記をシンプルにしたもの。その場で使い捨ての関数を、1行で書ける。

構文

lambda 引数列: 戻り値

通常の関数との対応

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

# 同じ意味のラムダ式
lambda x: x * 2
  • x で値を受け取る
  • x * 2 の結果が戻り値(return は書かない)

高階関数と組み合わせる

def で名前をつけずに、その場でラムダ式を渡せる。

def apply_list(func, items):
    result = []
    for x in items:
        result.append(func(x))
    return result

# 通常の関数を渡す書き方
def double(x):
    return x * 2
print(apply_list(double, [1, 2, 3]))             # → [2, 4, 6]

# ラムダ式を直接渡す(def を書かなくていい)
print(apply_list(lambda x: x * 2, [1, 2, 3]))    # → [2, 4, 6]
print(apply_list(lambda x: x + 3, [1, 2, 3]))    # → [4, 5, 6]
print(apply_list(lambda x: x * x, [1, 2, 3]))    # → [1, 4, 9]

ラムダ式を使うべき場面

  • その場で1回しか使わない処理: わざわざ名前をつける必要がない
  • 高階関数の引数として渡す処理: 引数として渡すなら、名前は呼び出し側に書けばOK

逆に、何度も使い回す関数や、複雑なロジックを持つ関数は普通に def で書くべき。

プレ演習5-4

プレ演習5-3のコード(apply_listdouble)を、ラムダ式を使って書き直そう。

期待される実行結果:

[2, 4, 6, 8, 10]

余裕のある人向け: ラムダ式で各要素を「3倍」や「-5」にしてみよう。


授業内演習

問1(提出不要)

次のプログラムの動作を確認しなさい。

def apply_list(func, items):
    """リストの各要素に指定された関数を適用する高階関数"""
    result = []
    for item in items:
        result.append(func(item))
    return result

def increment(x):
    """引数に1を加える関数"""
    return x + 1

def square(x):
    """引数の二乗を計算する関数"""
    return x * x

# 数値のリスト
data = [4, 2, 6, 5, -2]

# increment 関数を適用
incremented_data = apply_list(increment, data)
print('1を加えた結果:', incremented_data)

# square 関数を適用
squared_data = apply_list(square, data)
print('2乗した結果:', squared_data)

問2(要提出)

問1のプログラムにおいて、increment 関数と square 関数をラムダ式で書きなさい

基本課題

指定した関数をリストの各要素に適用し、新たなリストを返す関数 my_map を作成しなさい。さらに、与えられた数の絶対値を計算する関数 my_abs を作成し、関数 my_map を使って、キーボードから入力したリストの各要素の絶対値を出力するプログラムを作成しなさい。

約束ごと

  • 組み込み関数 map()abs() は使わないこと
  • 関数名: my_map(func, items)my_abs(x)

ヒント

  • my_map は授業内演習の apply_list と同じ構造
  • my_absif x < 0 で場合分けして、return -x または return x

動作確認

  • my_abs(-5) == 5my_abs(3) == 3my_abs(0) == 0
  • my_map(my_abs, [-3, 5, -1, 2, -7]) == [3, 5, 1, 2, 7]

応用課題

基本課題の「絶対値を計算する操作」をラムダ式を使用して行うようにプログラムを書き換えなさい。

約束ごと

  • my_absdef を消して、my_map に渡すところでラムダ式を直接書く
  • 組み込み関数 abs() は使ってもよい(ラムダの中で使うとシンプルに書ける)

ヒント

  • ラムダで絶対値: lambda x: abs(x) または lambda x: -x if x < 0 else x

動作確認

  • 基本課題と同じ結果になればOK
  • 入力 [-3, 5, -1, 2, -7] → 出力 [3, 5, 1, 2, 7]