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