Python Argument Binders

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.

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).

31409 次浏览

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 world\n")


print_hello()
Hello world

The above usage is equivalent to the following lambda.

print_hello = lambda *a, **kw: sys.stdout.write("Hello world\n", *a, **kw)

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

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()

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

>>> make_printer = lambda s: lambda: sys.stdout.write("%s\n" % 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

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

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

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