2008年10月19日日曜日

Python で部分適用 - functools モジュールの partial 関数

1. Python は一部の引数を与えて関数を呼び出すことができない

Haskell では、関数に複数の引数があるとき、先に一部の引数のみ渡しておき、後から残りの引数を渡すことができる。一部の引数を与えることを「部分適用」と言う。

Python では、普通そういうことはできない。例えば、3つの引数を足し合わせる関数 addThree 関数に対して、1つの引数だけ与える。

def addThree(a,b,c):
    return a + b + c

print addThree(1)

上記を実行すると、引数が足りないとエラーが表示される。

exceptions.TypeError: addThree() takes exactly 3 arguments (1 given)

 

2. functools の partial 関数で部分適用

6.6 functools モジュールの partial 関数を使うと、部分適用を利用できる。

partial(func[,*args][, **keywords])

Return a new partial object which when called will behave like func called with the positional arguments args and keyword arguments keywords.

例えば、先ほど定義した addThree 関数に対して、partial 関数を使い、引数を1つだけ与える。

import functools

addTwo = functools.partial(addThree,1)
print addTwo(2,3)

partial 関数により、addThree 関数の最初の引数だけ渡した関数 addTwo を作成した。その後、addTwo 関数に対して、残りの二つの引数を与えている。

以下では、部分適用をした結果から、更に部分適用した addOneFromTwo 関数を作成し、元の addThree に二つ引数を渡した関数 addOne を定義した。

addOneFromTwo = functools.partial(addTwo,2)
print addOneFromTwo(3)

addOne = functools.partial(addThree,1,2)
print addOne(3)

部分適用により、予め抽象的な関数を定義しておき、部分適用によって具体的な関数を導くことができる。上手く使えば、関数のモジュール化 を促進できる。

 

キーワード引数を利用して

キーワード引数を使うと、部分適用するときの引数を特定できる。

addTwo_ = functools.partial(addThree,c=3)
print addTwo_(1,2)

addOne_ = functools.partial(addTwo_,a=1)
print addOne_(b=2)

addOneFromTwo = functools.partial(addThree,a=1,c=3)
print addOneFromTwo(b=2)

 

3. reduce 関数に対して部分適用し、具体的な関数を導く

リストの要素を足し合わせる sum 関数は、畳み込み関数 reduce より導くことができる。

なぜ関数プログラミングは重要か によると、

sum = reduce add 0

reduce 関数に対して、部分適用することによって sum を定義している。

Python の reduce 関数の使い方は、

print reduce(lambda a,b: a+b, [1,2,3,4,5])

reduce 関数に対して部分適用を利用し、シーケンスを後で与えるようにして、sum 関数を定義してみる。

L = [1,2,3,4,5]

import operator
from functools import partial

# 合計
mysum = partial(reduce, operator.add)
print mysum(L)

(functools.partial と書くのが面倒だったので、import の仕方を変更した。)

上記の書き方から、sum 関数が reduce というメタ的な関数の特殊パターンであるという雰囲気が伝わって来る。

Python では、(+) のように、二項演算子を渡すことができないので、3.10 operator モジュールの add を利用した。

 

4. 関数を合成する関数を定義

Haskell の 関数を合成する(.) 関数を、partial を使い Python  で定義してみる。

関数を合成する関数名を `c’ とした。

c = lambda f,g: partial(lambda f,g,h: f(g(h)), f, g)

def … return で定義した方が読みやすいかな。

def c(f,g):
    return partial(lambda f,g,h: f(g(h)), f, g)

それとも、関数をネストして定義してみる。

def c(f,g):
    def _(f,g,h):
        return f(g(h))
    return partial(_,f,g)

c 関数を使ってみる。

add3 = lambda x: x+3
mul3 = lambda x: x*3

print c(add3,mul3)(10)   # 33
print c(mul3,add3)(10)   # 39

 

関連記事