Currying decorator in python

2020-01-29 05:51发布

I am trying to write a currying decorator in python, and I think I've got the general idea down, but still got some cases that aren't working right...

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

While for the following case this works fine:

myfun(5)
myfun(5)

For the following case it fails:

myfun(6)(7)

Any pointers on how to correctly do this would be greatly appreciated!

Thanks!

9条回答
孤傲高冷的网名
2楼-- · 2020-01-29 06:08

The below implementation is naive, google for "currying python" for more accurate examples.

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)
查看更多
啃猪蹄的小仙女
3楼-- · 2020-01-29 06:08

As it's cool to write currying decorators in python, I tried mine: 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
查看更多
【Aperson】
4楼-- · 2020-01-29 06:10

The solution from Roger Christman will not work with every constellation. I applied a small fix to also handle this situation:

curried_func(1)(2,3)

The small fix that makes it work with every constellation lies in the returned 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
查看更多
forever°为你锁心
5楼-- · 2020-01-29 06:15

This one is fairly simple and doesn't use inspect or examine the given function's 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
查看更多
再贱就再见
6楼-- · 2020-01-29 06:18

I think I've got a better one:

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

This solution uses Python's own functools.partial function instead of effectively recreating that functionality. It also allows you to pass in more arguments than the minimum, -allows keyword arguments,- and just passes through functions that don't have to take arguments, since those are pointless to curry. (Granted, the programmer should know better than to curry zero-arity or multi-arity functions, but it's better than creating a new function in that case.)

UPDATE: Whoops, the keyword argument part doesn't actually work right. Also, optional arguments are counted in the arity but *args are not. Weird.

查看更多
甜甜的少女心
7楼-- · 2020-01-29 06:25

Simplest way to curry a function in python is like this:

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

One can use it as follows:

_list = []
mask = "Test {}"
append_masked = curry(_list.append, mask.format)
for i in range(10):
    append_masked(i)

which will produce:

['Test 1', 'Test 2', 'Test 3' ... 'Test 10']
查看更多
登录 后发表回答