I am not able to get my head on how the partial
works in functools
.
I have the following code from here:
>>> sum = lambda x, y : x + y
>>> sum(1, 2)
3
>>> incr = lambda y : sum(1, y)
>>> incr(2)
3
>>> def sum2(x, y):
return x + y
>>> incr2 = functools.partial(sum2, 1)
>>> incr2(4)
5
Now in the line
incr = lambda y : sum(1, y)
I get that whatever argument I pass to incr
it will be passed as y
to lambda
which will return sum(1, y)
i.e 1 + y
.
I understand that. But I didn't understand this incr2(4)
.
How does the 4
gets passed as x
in partial function? To me, 4
should replace the sum2
. What is the relation between x
and 4
?
Roughly, partial
does something like this (apart from keyword args support, etc):
def partial(func, *part_args):
def wrapper(*extra_args):
return func(*part_args, *extra_args)
return wrapper
So, by calling partial(sum2, 4)
you create a new function (a callable, to be precise) that behaves like sum2
, but has one positional argument less. That missing argument is always substituted by 4
, so that partial(sum2, 4)(2) == sum2(4, 2)
As for why it's needed, there's a variety of cases. Just for one, suppose you have to pass a function somewhere where it's expected to have 2 arguments:
class EventNotifier(object):
def __init__(self):
self._listeners = []
def add_listener(self, callback):
''' callback should accept two positional arguments, event and params '''
self._listeners.append(callback)
# ...
def notify(self, event, *params):
for f in self._listeners:
f(event, params)
But a function you already have needs access to some third context
object to do its job:
def log_event(context, event, params):
context.log_event("Something happened %s, %s", event, params)
So, there are several solutions:
A custom object:
class Listener(object):
def __init__(self, context):
self._context = context
def __call__(self, event, params):
self._context.log_event("Something happened %s, %s", event, params)
notifier.add_listener(Listener(context))
Lambda:
log_listener = lambda event, params: log_event(context, event, params)
notifier.add_listener(log_listener)
With partials:
context = get_context() # whatever
notifier.add_listener(partial(log_event, context))
Of those three, partial
is the shortest and the fastest.
(For a more complex case you might want a custom object though).