How can I bind arguments to a function in Python?

Question:

How can I bind arguments to a Python function so that I can call it later without arguments (or with fewer additional arguments)?

For example:

def add(x, y):
    return x + y

add_5 = magic_function(add, 5)
assert add_5(3) == 8

What is the magic_function I need here?


It often happens with frameworks and libraries that people accidentally call a function immediately when trying to give arguments to a callback: for example on_event(action(foo)). The solution is to bind foo as an argument to action, using one of the techniques described here. See for example How to pass arguments to a Button command in Tkinter? and Using a dictionary as a switch statement in Python.

Some APIs, however, allow you to pass the to-be-bound arguments separately, and will do the binding for you. Notably, the threading API in the standard library works this way. See thread starts running before calling Thread.start. If you are trying to set up your own API like this, see How can I write a simple callback function?.

Explicitly binding arguments is also a way to avoid problems caused by late binding when using closures. This is the problem where, for example, a lambda inside a for loop or list comprehension produces separate functions that compute the same result. See What do lambda function closures capture? and Creating functions (or lambdas) in a loop (or comprehension).

Asked By: Dustin Getz

||

Answers:

functools.partial returns a callable wrapping a function with some or all of the arguments frozen.

import sys
import functools

print_hello = functools.partial(sys.stdout.write, "Hello worldn")

print_hello()
Hello world

The above usage is equivalent to the following lambda.

print_hello = lambda *a, **kw: sys.stdout.write("Hello worldn", *a, **kw)
Answered By: Jeremy

Using functools.partial:

>>> from functools import partial
>>> def f(a, b):
...     return a+b
... 
>>> p = partial(f, 1, 2)
>>> p()
3
>>> p2 = partial(f, 1)
>>> p2(7)
8
Answered By: Matthew Trevor

Functors can be defined this way in Python. They’re callable objects. The “binding” merely sets argument values.

class SomeFunctor( object ):
    def __init__( self, arg1, arg2=None ):
        self.arg1= arg1
        self.arg2= arg2
    def __call___( self, arg1=None, arg2=None ):
        a1= arg1 or self.arg1
        a2= arg2 or self.arg2
        # do something
        return

You can do things like

x= SomeFunctor( 3.456 )
x( arg2=123 )

y= SomeFunctor( 3.456, 123 )
y()
Answered By: S.Lott

If functools.partial is not available then it can be easily emulated:

>>> make_printer = lambda s: lambda: sys.stdout.write("%sn" % s)
>>> import sys
>>> print_hello = make_printer("hello")
>>> print_hello()
hello

Or

def partial(func, *args, **kwargs):
    def f(*args_rest, **kwargs_rest):
        kw = kwargs.copy()
        kw.update(kwargs_rest)
        return func(*(args + args_rest), **kw) 
    return f

def f(a, b):
    return a + b

p = partial(f, 1, 2)
print p() # -> 3

p2 = partial(f, 1)
print p2(7) # -> 8

d = dict(a=2, b=3)
p3 = partial(f, **d)
print p3(), p3(a=3), p3() # -> 5 6 5
Answered By: jfs

This would work, too:

def curry(func, *args):
    def curried(*innerargs):
       return func(*(args+innerargs))
    curried.__name__ = "%s(%s, ...)" % (func.__name__, ", ".join(map(str, args)))
    return curried

>>> w=curry(sys.stdout.write, "Hey there")
>>> w()
Hey there
Answered By: Claudiu

lambdas allow you to create a new unnamed function with fewer arguments and call the function:

>>> def foobar(x, y, z):
...     print(f'{x}, {y}, {z}')
... 
>>> foobar(1, 2, 3)  # call normal function
1, 2, 3
>>> bind = lambda x: foobar(x, 10, 20)  # bind 10 and 20 to foobar
>>> bind(1)
1, 10, 20
>>> bind = lambda: foobar(1, 2, 3)  # bind all elements
>>> bind()
1, 2, 3

You can also use functools.partial. If you are planning to use named argument binding in the function call this is also applicable:

>>> from functools import partial
>>> barfoo = partial(foobar, x=10)
>>> barfoo(y=5, z=6)
10, 5, 6

Note that if you bind arguments from the left you need to call the arguments by name. If you bind from the right it works as expected.

>>> barfoo(5, 6)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foobar() got multiple values for argument 'x'
>>> f = partial(foobar, z=20)
>>> f(1, 1)
1, 1, 20
Answered By: Alexander Oh

The question asks generally about binding arguments, but all answers are about functions. In case you are wondering, partial also works with class constructors (i.e. using a class instead of a function as a first argument), which can be useful for factory classes. You can do it as follows:

from functools import partial

class Animal(object):
    def __init__(self, weight, num_legs):
        self.weight = weight
        self.num_legs = num_legs
        
animal_class = partial(Animal, weight=12)
snake = animal_class(num_legs = 0)
print(snake.weight) # prints 12
Answered By: Ataxias
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.