hatunina’s blog

メモと日記です

高階関数とlambda式

パーフェクトPython 130Pあたりの内容です。

高階関数とは、関数を引数としてとったり戻り値として返す関数を指します。

例えば、こんな感じ。

def spam():
    print('spam!')

def ham(arg):
    print('ham!')
    arg()

def egg():
    print('egg!')
    return spam

spam()
ham(spam)
print('----------')
hoge = egg()
hoge()

実行結果

spam!
ham!
spam!
----------
egg!
spam!

ham()に渡した関数がham()の中で呼ばれてspam!と出力されます。
また、hogeという変数にegg()を代入しました。
この時点でegg()が呼ばれているのでegg!と出力され、hogeにはspamが代入されます。
なので、hoge()としてspamを実行するとspam!と出力されます。
あと、egg()で返されているspamがもしspam()であれば、returnのタイミングでspam()が呼ばれることになるので注意してください。

次は具体的な使い方を見ていきます。
書籍に沿って、奇数を取り出す処理を考えます。

def pick_odd(seq):
    ret = []
    for item in seq:
        if item % 2 == 1:
            ret.append(item)

    return ret

seq = range(10)
ret = pick_odd(seq)
print(ret)

実行結果

[1, 3, 5, 7, 9]

ここでは0から10までのシーケンスを作成しfor文で一つずつ取り出し奇数を取り出しました。
この処理を高階関数を使うとこんな感じに書けます。

def is_odd(item):
    return item % 2 == 1

def filter(pred, seq):
    ret = []
    for item in seq:
        if pred(item):
            ret.append(item)

    return ret

def pick_odd(seq):
    return filter(is_odd, seq)

seq = range(10)
ret = pick_odd(seq)
print(ret)

実行結果

[1, 3, 5, 7, 9]

is_odd関数とfilter関数を実装し関数を細分化しました。
filter関数のfor文でシーケンスの中身をpredとして渡されたis_odd関数を使用して奇数の判断をしています。
わかりづらくなっただけかのようにも思いますが、これの良いところは、奇数ではなく偶数が欲しくなった場合に別の関数を実装し、それをpredとして渡せば良いのです。
例えば、偶数を判定するis_even関数を実装しfilterへ引数として渡します。

def is_odd(item):
    return item % 2 == 1

def is_even(item):
    return item % 2 == 0

def filter(pred, seq):
    ret = []
    for item in seq:
        if pred(item):
            ret.append(item)

    return ret

def pick_odd(seq):
    return filter(is_even, seq)

seq = range(10)
ret = pick_odd(seq)
print(ret)

実行結果

[0, 2, 4, 6, 8]

こんな感じで引数を変えるだけで様々な処理に対応することができます。

また、is_odd関数やis_even関数はlambda式を利用することもできます。

def filter(pred, seq):
    ret = []
    for item in seq:
        if pred(item):
            ret.append(item)

    return ret

def pick_odd(seq):
    return filter(lambda item: item % 2 == 1, seq)

seq = range(10)
ret = pick_odd(seq)
print(ret)

実行結果

[1, 3, 5, 7, 9]

lambda式が引数predとして渡され、シーケンスの中身がlambda式のitemとして処理されます。
こっちの方がシンプルでスッキリしますね。

参考
gihyo.jp