Декорирование карри в питоне
Я пытаюсь написать 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)
Любые указатели на то, как правильно это сделать, будут очень признательны!
Спасибо!
Ответы
Ответ 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']
Ответ 6
Как здорово писать currying decorators в 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
Ответ 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