3

This is a question about programming style: what is the most "Pythonic" way to parameterize a function (is that even the right word for it?)

Say I have a function (e.g. an ODE solver) that accepts as an argument another function of two arguments (e.g. the ODE itself).

def solver(fun):
   # Implementation goes here
   # fun is a function handle that accepts two args e.g. fun(t,y)

However, the function I would like to pass into solver is parameterized by a third value

def myFun(t,y,A):
   # Implementation goes here

I have been dealing with this situation using lambda functions as follows:

A = 4
solution = solver(lambda t,y:myFun(t,y,A))

I've recently been seeing some posts online telling me to avoid lambdas like the plague, and that Guido himself regrets allowing that feature. If lambdas are indeed terrible, what's the better "Pythonic" way to implement the above? Without lambda I run into the problem of being unable to access the global namespace, i.e. I want to do this:

A = 4
def myFunNotLambda(t,y):
    return myFun(t,y,A)
solution = solver(myFunNotLambda)

but the only way to do this seems to be making A global, which is definitely far worse than using lambda

dkv
  • 6,602
  • 10
  • 34
  • 54
  • 2
    there's also `functools.partial` – Jean-François Fabre Jun 12 '17 at 18:47
  • 3
    There's no need to avoid lambdas like the plague. That said, `def` can do anything `lambda` can; you just forgot to `return` the value. `functools.partial` also works, although it can't fix positional arguments that don't occur at the front. – user2357112 Jun 12 '17 at 18:49
  • Guido's main objection to `lambda` is when people use it to wrap a simple expression to pass to `map` (or `filter`) instead of directly executing the expression in a list comp or gen exp, eg `map(lambda x:5*x+1, seq)` vs `(5*x+1 for x in seq)`. Notice that the `map`version incurs the overhead of a Python function call on every item in `seq`. Of course, this is irrelevant if the callable you pass to `map` is implemented in C, eg `map(int, list_of_strings)` is perfectly fine. – PM 2Ring Jun 12 '17 at 19:38
  • @user2357112 barring the missing `return` (fixed now), I think that still won't work since the value of `A` does not exist inside the `myFunNotLambda` namespace – dkv Jun 12 '17 at 20:32
  • 1
    @dkv: Why do you think it doesn't? Did you define `myFunNotLambda` outside the scope of `A`? – user2357112 Jun 12 '17 at 22:34
  • Ah, I see. I thought the only things accessible from inside a function definition were the arguments passed directly into it. I now see that everything in the parent namespace is also accessible. – dkv Jun 12 '17 at 23:38

3 Answers3

5

You can use functools.partial for that, like:

from functools import partial

A = 4
solution = solver(partial(myFun,A=A))

partial(..) constructs given a function (here myFunc) another function where the A parameter has now a default value with A.

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • 1
    +1 This nicely decouples any logic of passing arguments to the caller's function. Additionally, this increases the flexibility of `solver`, effectively allowing for `myFun` to accept any number of constant arguments. – jpmc26 Jun 12 '17 at 19:25
  • Is the overhead incurred from constructing this function the same as the overhead from constructing a `lambda`? – dkv Jun 12 '17 at 20:55
  • 2
    @dkv: constructing or calling? Because usually constructing happens only once and is thus not significant. [This answer](https://stackoverflow.com/a/11828489/67579) suggests that partial is faster than lambda. – Willem Van Onsem Jun 12 '17 at 21:09
  • I (perhaps poorly) assumed calling it would be identical, so I was asking about constructing. Naively, I would think the partial function would need to be re-defined at every iteration of the solver. – dkv Jun 12 '17 at 21:15
  • 3
    @dkv `partial` returns a full fledged callable (looks like an instance of a class). It would only get constructed by the caller when they pass it in. If you have a loop with a lot of iterations, it's calling time you want to worry about. That said, I'd argue you're talking about a micro-optimization. I'd profile it and see if it's a problem first. – jpmc26 Jun 12 '17 at 21:16
  • 3
    @dkv The construction of the `partial` object is a one-off cost, once it's constructed it doesn't need to be be construced again, unless you need a new one with a different `A` parameter. But when you call the partial object, it then has to call the original `myFun`, so you get two levels of Python function / method call, just like in your lambda-based solution. – PM 2Ring Jun 12 '17 at 21:23
3

Advice to "avoid lambda like the plague" is a serious exaggeration at best, and pure FUD at worst - grepping lambda in Python's standard library reveals literally thousands of matches.

While it is true that Guido has expressed later concern about lambda-dominated coding style, this was in context of (over)using functional constructs like map and reduce, which are better expressed in Python using list comprehensions and generator expressions.

When it comes to creating temporary functions like the one you need, however, there is exactly nothing wrong with using lambda; on the contrary, it's the best available tool for the job. If you still want to avoid lambda keyword, you can use a nested def:

def my_particular_fun(t, y):
    return my_fun(t, y, 4)

solution = solver(my_particular_fun)

As presented in other answers, it is also possible to use functools.partial to emulate a lambda, although it comes at a cost of fixing the name of the third parameter to my_fun.

user4815162342
  • 141,790
  • 18
  • 296
  • 355
  • 1
    The problem with `my_particular_fun` is that it incurs the cost of a double function call, and Python calls are relatively slow. – PM 2Ring Jun 12 '17 at 19:01
  • @PM2Ring That is the case with any solution other than rewriting `my_fun` to hard-code the `A` argument to 4. Also, worrying about the speed of a single additional *function call* without having any idea what the function is doing sounds like premature optimization. – user4815162342 Jun 12 '17 at 19:03
  • Presumably the solver is calling its `fun` arg in a loop, so halving the number of calls involved will probably make a noticeable speed difference. – PM 2Ring Jun 12 '17 at 19:12
  • 1
    You don't need to hard-code the `A` to a fixed value, you can make it available via a closure, as I show in my answer. OTOH, I normally just use a lambda in this situation, or occasionally `partial`, unless the solver is taking a long time & I'm desperate for speed-ups. :) – PM 2Ring Jun 12 '17 at 19:32
  • @PM2Ring We don't know how the solver is calling the function, and I would expect the inner loop to be implemented in numpy anyway. As for "hard-coding" A, there is absolutely no harm is using a constant, as long as the constant is local and specific to a function like `my_particular_fun`. Avoiding constants at all costs leads to obfuscated code, such as the infamous `#define ZERO 0` C anti-patterns. Also, in Python a constant actually avoids a name lookup, making the code more efficient. :) – user4815162342 Jun 12 '17 at 19:48
  • 1
    Fair enough, although the OP didn't mention they were using Numpy, but if they aren't they're probably using something else that can do fast mathematics, eg mpmath, which uses gmpy if it's available. And you are certainly correct that using a constant in Python avoids a name lookup, although we don't know that the OP's `A` actually _is_ a constant. ;) – PM 2Ring Jun 12 '17 at 20:01
  • To clarify (for the sake of being pedantic, since I don't think it's actually relevant to the answer), I'm in fact looping over many different values of `A` so the nested `def`won't work for my specific case (although it's valid for my particular example in the question). Also, I think any numerical integrator has to call the function repeatedly regardless of the implementation so a double call would indeed cause a slowdown. – dkv Jun 12 '17 at 20:54
  • 1
    @dkv Unless you can rewrite `my_fun`, there is simply no avoiding the double call. `lambda` does it, the nested `def` does it, and `functools.partial` does it too. The additional function call will be noticable only if the rest of the function is truly trivial. – user4815162342 Jun 12 '17 at 20:59
  • BTW I don't understand why a nested `def` wouldn't work for you. You can certainly use it in a loop, and replace the hard-coded value with a calculated value. Python is smart to create the function's code only once, so each `def` will simply create a small wrapper that calls the same (pre-created) code with a new captured value. This is what a "closure" is about, and what both `lambda` and nested `def` do under the hood. – user4815162342 Jun 12 '17 at 21:00
  • Sorry, had a misconception about how namespaces work. A nested `def` would work. – dkv Jun 12 '17 at 23:52
1

A quite efficient way to do this is to use functools.partial, but as has been pointed out, partial only lets you "freeze" final args. If you need something else, you can easily implement your own version using a closure.

This approach is actually a little more efficient than using a partial object, since when you call the partial it still has to call the original function that you pass in, so each call of the partial results in two calls, and Python function / method calls are relatively slow. If you're curious, take a look at the Python source code for functools.partial.

In this example, I've made A the second (pseudo) parameter of the function, since partial nicely handles the case where it's the last arg.

def my_partial(param):
    A = param
    print('Creating two_arg_func with A ==', A)
    def two_arg_func(t, y):
        # Do something with all the args
        return 'args', t, A, y
    return two_arg_func

def test(f):
    for u in range(10, 50, 10):
        print(f(u, u + 5))

test(my_partial(7))

output

Creating two_arg_func with A == 7
('args', 10, 7, 15)
('args', 20, 7, 25)
('args', 30, 7, 35)
('args', 40, 7, 45)

We don't really need param in my_partial, we can just use the passed in arg, since that's local to my_partial:

def my_partial(A):
    print('Creating two_arg_func with A ==', A)
    def two_arg_func(t, y):
        return 'args', t, A, y
    return two_arg_func

From your comments, I now understand that you want to be able to vary A. Of course you can do that by calling partial or my_partial again, but if you want to modify A a lot that's not so efficient.

Your comments indicate that you want to modify A in the global context, so you might as well just use a global. You don't need to actually put the code modifying A into the global context, you can wrap it in a function, but of course you will need to use the global directive in the function that modifies A. You do not need the global directive in any function which merely reads the value of A, though.

Here's a short demo.

def two_arg_func(t, y):
    # Do something with the args and with A
    return 'args', t, A, y

def solve(f):
    for u in range(10, 40, 10):
        print('SOLVER', f(u, u + 5))

def test(f):
    global A
    for A in range(7, 10):
        print(A)
        solve(f)

test(two_arg_func)

output

7
SOLVER ('args', 10, 7, 15)
SOLVER ('args', 20, 7, 25)
SOLVER ('args', 30, 7, 35)
8
SOLVER ('args', 10, 8, 15)
SOLVER ('args', 20, 8, 25)
SOLVER ('args', 30, 8, 35)
9
SOLVER ('args', 10, 9, 15)
SOLVER ('args', 20, 9, 25)
SOLVER ('args', 30, 9, 35)

However, that previous solution is somewhat unsatisfactory, since the main thrust of your question was how to do this without using a global. So here's a slight variation on that code. Instead of putting A in the global namespace, we attach it to two_arg_func as a function attribute. We can do this because a Python function is an object, and it already has a whole bunch of attributes; two you may be familiar with are __name__ and __doc__. Anyway, here's the new code, it prints the same output as the previous version.

def two_arg_func(t, y):
    A = two_arg_func.A
    # Do something with the args and with A
    return 'args', t, A, y

def solve(f):
    for u in range(10, 40, 10):
        print('SOLVER', f(u, u + 5))

def test(f):
    for A in range(7, 10):
        print(A)
        f.A = A
        solve(f)

test(two_arg_func)
PM 2Ring
  • 54,345
  • 6
  • 82
  • 182
  • Doesn't `my_partial` have the same issue of a double call? i.e. when you call `test`, it calls `my_partial` four times, and each of those four times it defines and immediately calls `two_arg_func`? – dkv Jun 12 '17 at 21:02
  • 1
    @dkv No, in my code `my_partial` is only called once, in the final line. `two_arg_func` is only defined once, just after `Creating two_arg_func with A == 7` gets printed (in fact, I added that `print` call there to demonstrate that). It will only create a new function if you call `my_partial` again, eg if you need a different `A` value. The loop inside `test` makes 4 calls, and those calls are directly to the function that was returned by the one and only call to `my_partial(7)`. – PM 2Ring Jun 12 '17 at 21:13
  • Oh, right, sorry, the print statement does make it very clear. Is that some built-in Python magic, or is there something obvious I'm missing? – dkv Jun 12 '17 at 21:21
  • @dkv I'm not quite sure what magic you're talking about. :) Python functions are first-class objects, so it's quite simple for them to be passed as arguments, or to be returned as return values. I guess one magic thing is how `two_arg_func` gets access to `A`. It does that via a closure cell. See this answer about [closures](https://stackoverflow.com/a/14414638/4014959) which I also linked in the answer. – PM 2Ring Jun 12 '17 at 21:29
  • If you unroll the for-loop, what's happening is effectively `my_partial(7)(10,15); my_partial(7)(20,25); my_partial(7)(30,35); my_partial(7)(40,45)`. It seems "magic" that `two_arg_func` gets created only once instead of four times since I would expect `my_partial` to be called four times (as you mentioned, if I understood correctly, this is actually what happens in `lambda` and in `functools.partial`). – dkv Jun 12 '17 at 21:37
  • 1
    @dkv No, when you unroll the loop `f` is called 4 times, and that `f` is the function object returned by the _single_ call to `my_partial`. However, from reading your comments to user4815162342 it appears that we've misunderstood what you want to do with `A`. We all thought that you wanted a fixed value of `A` but you don't. Oops! I'll update my answer shortly. – PM 2Ring Jun 12 '17 at 21:59
  • This is actually a super-cool solution and definitely the most informative from the perspective of Python features - I will probably end up using it when I need to squeeze some extra performance out of my code. Thank you for the detailed answer. Although, I think I'm obligated to accept the answer that most directly answers the question in the title. – dkv Jun 13 '17 at 00:01