4

I am trying create a list of lambdas for deferred execution using a list comprehension. The below is a simple example.

def func_a(message: str) -> None:
    print('a: ' + message)
    
def func_b(message: str) -> None:
    print('b: ' + message)

msg = 'some message'
funcs = [func_a, func_b]
funcs_w_args = [lambda : func(msg) for func in funcs]

for func in funcs_w_args:
    func()

The result is

b: some message
b: some message

Whereas the desired result should be

a: some message
b: some message

Where am I going wrong?

TylerH
  • 20,799
  • 66
  • 75
  • 101
OldSchool
  • 459
  • 1
  • 3
  • 14
  • (hopefully can fit as a comment) I think you are running into name collision issues - the lambda says "lookup the function 'func' and run it". After the list comprehension, the name "func" -> func_b. Basically, func isn't closed until you actually run the lambda, so it's not giving what you expect. I am having trouble proving this (I thought func would have a value after the comprehension, but it doesn't) but i would bet it's something along those lines. – Corley Brigman Jul 13 '20 at 21:30
  • (which, actually, is exactly what @Carcigenicate 's linked question was showing, so go there for better examples :) – Corley Brigman Jul 13 '20 at 21:35
  • @Carcigenicate. Thanks for the link. It provides a thorough discussion of the topic. For a quick and direct answer However, I'd recommend CypherX's below. – OldSchool Jul 17 '20 at 09:14

1 Answers1

5

Solution

What you are trying to achieve is defining partial functions (more generally). You can do this using functools.partial.

Here's how:

from functools import partial

# Your Code
def func_a(message: str) -> None:
    print('a: ' + message)
    
def func_b(message: str) -> None:
    print('b: ' + message)

msg = 'some message'
funcs = [func_a, func_b]

# What I changed: a list of partially-defined functions
funcs_w_args = [partial(func, msg) for func in funcs]

# Now call partially defined functions
for func in funcs_w_args:
    func()

Output:

a: some message
b: some message

Refernces

CypherX
  • 7,019
  • 3
  • 25
  • 37
  • 1
    While I consider the question a duplicate, I thank you for placing a `functools.partial` solution here. `functools.partial` is ideal for the task and doesn't get nearly enough love. The traditional idiom abusing default arguments is just that - abusive, and only considered an idiom out of tradition. Unfortunately, these idioms have a **lot** of inertia (see also: `x[:]` used where `x.copy()` is appropriate). – Karl Knechtel Jul 17 '20 at 09:33