Python で部分適用

Python で部分適用する場合は、functoolsモジュールのpartial関数を使います。 こんな感じです。

from functools import partial

def print_name(first_name, last_name):
    print(first_name + ' ' + last_name)

print_name_kurt = partial(print_name, 'Kurt')
print_name_kurt('Rosenwinkel')
#-> Kurt Rosenwinkel

Haskell などの純粋関数型言語では、partial関数のような道具を使わなくても、引数の数が足りない場合は自動的に部分適用のようになるらしいです(よく知らずに書いてます)。 Python 風疑似コードで書くと下のような感じでしょうか。

def print_name(first_name, last_name):
    print(first_name + ' ' + last_name)

print_name_kurt = print_name('Kurt')
print_name_kurt('Rosenwinkel')
#-> Kurt Rosenwinkel

実際に使用したい場面があるのか分かりませんが、 上記のように引数の数が足りないときに自動的に部分適用となるデコレータを書いてみました。

from functools import partial

def auto_partial(func):
    if isinstance(func, partial):
        args_count = 0
        inner_func = func
        while isinstance(inner_func, partial):
            args_count += len(inner_func.args)
            inner_func = inner_func.func
        original_func_argcount = inner_func.__code__.co_argcount
        func_argcount = original_func_argcount - args_count
    else:
        func_argcount = func.__code__.co_argcount

    def _partial(*args):
        if (func_argcount - len(args)) == 0:
            return func(*args)
        else:
            return auto_partial(partial(func, *args))

    return _partial

こんな感じで使えます。

@auto_partial
def print_name(first_name, last_name):
    print(first_name + ' ' + last_name)

print_name_kurt = print_name('Kurt')
print_name_kurt('Rosenwinkel')
#-> Kurt Rosenwinkel

※ 本ブログの Python コード例は Python 3 で動作するように書いています。