Карри декоратор в питоне - PullRequest
       16

Карри декоратор в питоне

17 голосов
/ 27 февраля 2012

Я пытаюсь написать карри-декоратор на 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 ]

26 голосов
/ 27 февраля 2012

Приведенная ниже реализация наивна, для более точных примеров Google использует "curry 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)
7 голосов
/ 28 октября 2013

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

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

Он обрабатывает args, kwargs,встроенные функции и обработка ошибок.Он даже оборачивает строки документов обратно на карри.

3 голосов
/ 08 июня 2015

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

Цитата из Википедии :

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

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

def unwrap(f):
    try:
        return unwrap(f.__wrapped__)
    except AttributeError:
        return f
2 голосов
/ 01 августа 2014

Поскольку писать написанные карри декораторы на 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
1 голос
/ 15 марта 2017

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

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
0 голосов
/ 17 марта 2018

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

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
0 голосов
/ 30 июля 2014

Самый простой способ карри функции в 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']
0 голосов
/ 15 мая 2014

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

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
0 голосов
/ 27 июня 2013

Думаю, у меня получше:

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 вместо эффективного воссоздания этой функции.Он также позволяет передавать больше аргументов, чем минимум, - позволяет использовать ключевые аргументы - и просто проходит через функции, которые не должны принимать аргументы, поскольку они бессмысленны для карри.(Конечно, программист должен знать лучше, чем создавать функции с нулевой или множественной арностью, но в этом случае это лучше, чем создавать новую функцию.)

ОБНОВЛЕНИЕ: Упс,часть аргумента ключевого слова на самом деле не работает правильно.Кроме того, необязательные аргументы учитываются в арности, а * аргументы - нет.Weird.

...