Currying декоратор в python

Я пытаюсь написать декоратора Карри на python, и я думаю, что у меня есть общая идея, но все еще есть некоторые случаи, которые работают неправильно...

def curry(fun):

    cache = []
    numargs = fun.func_code.co_argcount

    def new_fun(*args, **kwargs):
        print args
        print kwargs
        cache.extend(list(args))

        if len(cache) >= numargs:   # easier to do it explicitly than with exceptions

            temp = []
            for _ in xrange(numargs):
                temp.append(cache.pop())
            fun(*temp)

    return new_fun


@curry
def myfun(a,b):
    print a,b

В то время как для следующего случая это работает нормально:

myfun(5)
myfun(5)

для следующего случая он терпит неудачу:

myfun(6)(7)

любые указатели на то, как правильно это сделать, будут очень признательны!

спасибо!

9 ответов


приведенная ниже реализация наивна, google для "карринга python" для более точных примеров.

def curry(x, argc=None):
    if argc is None:
        argc = x.func_code.co_argcount
    def p(*a):
        if len(a) == argc:
            return x(*a)
        def q(*b):
            return x(*(a + b))
        return curry(q, argc - len(a))
    return p

@curry
def myfun(a,b,c):
    print '%d-%d-%d' % (a,b,c)



myfun(11,22,33)
myfun(44,55)(66)
myfun(77)(88)(99)

исходный код curry на toolz библиотека доступен по следующей ссылке.

https://github.com/pytoolz/toolz/blob/master/toolz/functoolz.py

он обрабатывает args, kwargs, встроенные функции и обработку ошибок. Он даже обертывает завязки обратно на объект Карри.


многие из ответов здесь не учитывают тот факт, что функция Карри должна принимать только один аргумент.

цитата Википедия:

в математике и информатике, currying метод перевод оценки функции, которая принимает несколько аргументов (или кортеж аргументов) для оценки последовательности функций, каждый с одним аргументом (частичным приложение.)

выбор, чтобы украсить его С рекурсия и без co_argcount делает для прилично элегантного решения.

from functools import partial, wraps, reduce

def curry(f):
    @wraps(f)
    def _(arg):
        try:
            return f(arg)
        except TypeError:
            return curry(wraps(f)(partial(f, arg)))
    return _

def uncurry(f):
    @wraps(f)
    def _(*args):
        return reduce(lambda x, y: x(y), args, f)
    return _

как показано выше, также довольно тривиально писать uncurry декоратор. :) К сожалению, результирующая функция uncurried позволит любое количество аргументов вместо того, чтобы требовать определенного количества аргументов, поскольку это может быть неверно для исходной функции, поэтому она не является истинным обратным curry. Истинным обратным в этом случае было бы что-то вроде unwrap, но это потребует curry использовать functools.wraps или что-то подобное, что задает __wrapped__ атрибут для каждой вновь созданной функции:

def unwrap(f):
    try:
        return unwrap(f.__wrapped__)
    except AttributeError:
        return f

Это довольно просто и не использует inspect или examine args данной функции

import functools


def curried(func):
    """A decorator that curries the given function.

    @curried
    def a(b, c):
        return (b, c)

    a(c=1)(2)  # returns (2, 1)
    """
    @functools.wraps(func)
    def _curried(*args, **kwargs):
        return functools.partial(func, *args, **kwargs)
    return _curried

самый простой способ Карри функции в python выглядит так:

from functools import partial
curry = lambda f, g: partial(
    lambda F, G, *args, **kwargs: F(G(*args,**kwargs)),
    f, g
)

https://gist.github.com/hkupty/0ba733c0374964d41dec

можно использовать его следующим образом:

_list = []
mask = "Test {}"
append_masked = curry(_list.append, mask.format)
for i in range(10):
    append_masked(i)

который произведет:

['Test 1', 'Test 2', 'Test 3' ... 'Test 10']

Как здорово писать декораторов Карри в python, я попробовал мой: 5 строк кода, читаемая и проверенная функция Карри.

def curry(func):
    def curried(*args, **kwargs):
        if len(args) + len(kwargs) >= func.__code__.co_argcount:
            return func(*args, **kwargs)
        return (lambda *args2, **kwargs2:
                curried(*(args + args2), **dict(kwargs, **kwargs2)))
    return curried

вот моя версия карри, которая не использует частичную и заставляет все функции принимать ровно один параметр:

def curry(func):
"""Truly curry a function of any number of parameters
returns a function with exactly one parameter
When this new function is called, it will usually create
and return another function that accepts an additional parameter,
unless the original function actually obtained all it needed
at which point it just calls the function and returns its result
""" 
def curried(*args):
    """
    either calls a function with all its arguments,
    or returns another functiont that obtains another argument
    """
    if len(args) == func.__code__.co_argcount:
        ans = func(*args)
        return ans
    else:
        return lambda x: curried(*(args+(x,)))

return curried

Я думаю, что у меня есть лучше:

def curried (function):
    argc = function.__code__.co_argcount

    # Pointless to curry a function that can take no arguments
    if argc == 0:
        return function

    from functools import partial
    def func (*args):
        if len(args) >= argc:
            return function(*args)
        else:
            return partial(func, *args)
    return func

это решение использует собственный Python functools.partial функция вместо эффективного воссоздания этой функции. Он также позволяет передавать больше аргументов, чем минимум, -позволяет аргументы ключевых слов,- и просто проходит через функции, которые не должны принимать аргументы, так как они бессмысленны для карри. (Конечно, программист должен знать лучше, чем Карри нулевых или мультиаритетных функций, но это лучше, чем создание нового функция в этом случае.)

обновление: Упс, часть аргумента ключевого слова на самом деле не работает правильно. Кроме того, необязательные аргументы подсчитываются в arity, но *args-нет. Странный.


решение от Роджера Кристмана не будет работать с каждым созвездием. Я применил небольшое исправление, чтобы также справиться с этой ситуацией:

curried_func(1)(2,3)

небольшое исправление, которое заставляет его работать с каждым созвездием, лежит в возвращенной лямбде:

def curried(func):
    def curry(*args):
        if len(args) == func.__code__.co_argcount:
            ans = func(*args)
            return ans
        else:
            return lambda *x: curry(*(args+x))
    return curry