23

I am trying to write a currying decorator in python. I got this far:

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)

How can I do this correctly?


If you're just looking to bind arguments to a function and aren't interested in a specific design or the underlying computer science principles, see Python Argument Binders.

4
  • Why not use something like partial in functools modules? link Commented Feb 26, 2012 at 23:48
  • 6
    @digivampire: Because it doesn't do currying, probably. Commented Feb 27, 2012 at 0:00
  • To write a decorator for what purpose? Commented Feb 27, 2012 at 8:01
  • Wouldn't it be more useful to have a function that takes a function and curries it? Similar to Racket's curry ? Commented Apr 15, 2012 at 15:38

10 Answers 10

27

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)
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks man! Yah, I realized it needed to be recursive, but just had no idea how to implicitly create a function with n-1 arguments. Very cool!
in python 2.6 or python 3, the line 3 should written as: argc = x.__code__.co_argcount
"Google for currying python": that's funny because it's now the top google result.
13

The source code for curry in the toolz library is available at the following link.

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

It handles args, kwargs, builtin functions, and error handling. It even wraps the docstrings back onto the curried object.

3 Comments

This is by far the best answer, and using the cytoolz version of the library is very fast. I don't think any other answer on here handles default arguments correctly.
This is deprecated now.
That is not true. Toolz.curry is not deprecated.
5

Many of the answers here fail to address the fact that a curried function should only take one argument.

A quote from Wikipedia:

In mathematics and computer science, currying is the technique of translating the evaluation of a function that takes multiple arguments (or a tuple of arguments) into evaluating a sequence of functions, each with a single argument (partial application).

Choosing to decorate it with recursion and without co_argcount makes for a decently elegant solution.

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 _

As shown above, it is also fairly trivial to write an uncurry decorator. :) Unfortunately, the resulting uncurried function will allow any number of arguments instead of requiring a specific number of arguments, as may not be true of the original function, so it is not a true inverse of curry. The true inverse in this case would actually be something like unwrap, but it would require curry to use functools.wraps or something similar that sets a __wrapped__ attribute for each newly created function:

def unwrap(f):
    try:
        return unwrap(f.__wrapped__)
    except AttributeError:
        return f

Comments

4

One-liner just for fun:

from functools import partial
curry = lambda f: partial(*[partial] * f.__code__.co_argcount)(f)

@curry
def add(x, y, z):
    return x + y + z

print(add(2)(3)(4))
# output = 9

1 Comment

Works like a charm! But could you please explain a bit what magic you are doing :D Is there a way to reverse arguments given to a target function? I want to use it within a pipe where target function would get initial value of the pipe and then each function would return the value to be used in the next function. It works nicely already with just defining initial value coming from a pipe as the last parameter def change_age(new_age, person): but it just feels a bit clunky :D
3

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

1 Comment

Can you extend this method to curry_at a user-specified index position?
3

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

3 Comments

Julien, can your method be easily extended to curry at a specified position (partial eval)?
@alancalvitti did you tried functools.partial?
I've tested it but I don't see how it can automatically select the specified argument. For example, I'm looking for curry_at(2) (in general, curry_at(n)) to extract the 2nd (nth) argument in args for partial eval.
2

Here is my version of curry that doesn't use partial, and makes all the functions accept exactly one parameter:

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

1 Comment

Hi, this one is not working on all edge cases. I added the solution (with code) in my answer. By the way, I think this solution is by far most intuitive one!
0

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.

Comments

0

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

Comments

0

This is my lambda styled code (and its refs):

# ref: https://rosettacode.org/wiki/Y_combinator#Python
fixedpoint = lambda f: (lambda x: x(x)) (lambda y: f(lambda *args: y (y) (*args))) ;

# ref: https://python-course.eu/advanced-python/currying-in-python.php#Decorator-for-currying
curry = lambda func: fixedpoint (lambda curried: 
    lambda *args: (lambda x: curried (* (args + (x,)))) if 
    (len (args) != func.__code__.co_argcount) else 
    func (*args)) ;

# license: gpl-2.0. if you have better ideas show them plz ...

Uses:

# e.g. def
@curry
def prod3 (x, y, z):
    return x + y + z

print (prod3 (3) (4) (5)) # 12

# e.g. lambda
prod_3 = curry (lambda x,y,z: x + y + z)
print (prod_3 (6) (7) (8)) # 21

The function while call curry (f) returns, will pick args one-by-one until the coumt of them equal with the function f which be given to the calling curry (f), then that f can be call by those rgs we've just picked.

You can try them on shinylive or RustPython Demo. Enjoy it 🫡.


Or you can:

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.