Подтвердить что ты не робот

Декорирование карри в питоне

Я пытаюсь написать currying decorator в 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)

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

Спасибо!

4b9b3361

Ответ 1

Нижеприведенная реализация наивна, google для "currying 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)

Ответ 2

Исходный код curry в toolz library доступен по следующей ссылке.

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

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

Ответ 3

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

Цитата из Wikipedia:

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

Выбор декорации с рекурсией и без 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 требуется curry использовать functools.wraps или что-то подобное, которое устанавливает атрибут __wrapped__ для каждой вновь созданной функции:

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

Ответ 4

Это довольно просто и не использует проверку или проверку заданной функции 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

Ответ 5

Самый простой способ выполнить функцию в 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']

Ответ 7

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

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

Ответ 8

Я думаю, у меня есть лучший вариант:

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

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

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

Ответ 9

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

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