Search code examples
pythonfunctioncoding-styleclosuresmetaprogramming

Pythonic way to define function closures


I have some function defined in a module mymodule:

def f1(a, b):
    # do something with a and b

For a number of design reasons, in the main script I need to call f1 from a higher-order function mycaller, which will only pass a as an argument. The other parameter b is only available in the main script.

Internally, mycaller looks as follows:

  def mycaller(list_of_functions, a):
        for f in list_of_functions:
             f(a)

So far, I have done

from mymodule import f1

bb = myB()
aa = myA()

def closure_f1(a):
    return f1(a, bb)

mycaller([closure_f1], aa)

Is this the correct way of designing such a closure? Right now I end up with many closures in main script which looks ugly and I believe might also give problems with serialization. How can I improve this?

Here are some attempts that I made without a separate closure_f1 function:

mycaller([f1],a)

However, then the function misses the b argument. Then I have tried

mycaller([f1(b)],a)

but of course this lead to a function call (which fails) rather than in passing the function with argument b set.


Solution

  • Several possibilities:

    1. Use functools.partial

    If you want to fix the first argument (or first few arguments), just use functools.partial:

    from functools import partial
    mycaller([partial(f1, bb)], aa)
    

    For this to work with your example you would have to swap the order of arguments a and b in the definition of f1.


    2. Define your own partial that fixes the second argument

    If you want to fix the second argument, the functools.partial doesn't help, because it works from left to right. But if you have a lot of functions where you have to fix the second argument, you could do something like this:

    def partial2(f, y):
      def res(x):
        return f(x, y)
      return res
    

    and then use it as follows:

    mycaller([partial2(f1, bb)], aa)
    

    3. Just use lambdas:

    This is a somewhat verbose, but at the same time the most flexible possibility:

    mycaller([lambda x: f1(x, bb)], aa)
    

    An explicit lambda could be preferable if you don't want to pollute the namespace by a bunch of trivial function definitions.

    4. Just define a helper function

    I don't see anything catastrophic in defining a helper function, especially if it is used often. So, just leave closure_f1 as-is and use:

    mycaller([closure_f1], aa)