在python的Currying装饰员

问题描述 投票:16回答:9

我正在尝试在python中写一个currying装饰器,我想我已经有了一般的想法,但仍然有一些不正常的情况......

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)

任何有关如何正确执行此操作的指示将非常感谢!

谢谢!

python decorator currying
9个回答
27
投票

以下实现是天真的,谷歌为“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)

7
投票

currytoolz library的源代码可从以下链接获得。

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

它处理args,kwargs,内置函数和错误处理。它甚至将文档字符串包裹回到咖喱对象上。


4
投票

这里的许多答案都没有解决这个问题,即一个curried函数应该只采用一个参数。

引自Wikipedia

在数学和计算机科学中,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

3
投票

因为在python中编写currying装饰器很酷,我尝试了我的:5 lines of code, readable, and tested curry function

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
投票

这个很简单,不使用检查或检查给定函数的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

1
投票

这是我的咖喱版本,不使用partial,并使所有函数只接受一个参数:

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
投票

我想我有一个更好的:

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函数,而不是有效地重新创建该功能。它还允许您传递比最小值,-allows关键字参数更多的参数,并且只传递不必参数的函数,因为这些函数对于咖喱来说毫无意义。 (当然,程序员应该知道比调整零度或多元函数更好,但它比在这种情况下创建一个新函数更好。)

更新:哎呀,关键字参数部分实际上并不正常。此外,可选参数在arity中计算,但* args不计算。奇怪的。


0
投票

在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
投票

Roger Christman的解决方案不适用于每个星座。我应用了一个小修复程序来处理这种情况:

curried_func(1)(2,3)

使其适用于每个星座的小修复在于返回的lambda:

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
© www.soinside.com 2019 - 2024. All rights reserved.