Python: assignment in lambdas, take 2. Use tuples and operator :=

This is a follow up on this post.

Default parameters are not the only way to do complex calculations in Python lambdas. Another idea is to use tuples and the “walrus” operator := available since Python 3.8. E.g., if we want to calculate (x2 + 2x + 7) + sqrt(x2 + 2x + 7), we can write a lambda as follows:

mymath = lambda x: (
    y := x**2 + 2*x + 7,
    y + sqrt(y)
)[-1]

Credit goes to 👤fleming for suggesting this brilliant option.

Let’s dissect what we are doing here:

  1. The lambda creates a tuple.
  2. The tuple has two items.
  3. The first item creates a variable named y and assigns a value.
  4. The second item uses this variable.
  5. Finally, the lambda returns the last item for the tuple by applying the [-1] index.

Let’s consider a more interesting example:

delete_file = lambda file_path: (
    os := __import__('os'),
    exists := os.path.exists(file_path),
    os.remove(file_path) if exists else None,
    exists
)[-1]

# create a test file
test_file = "/tmp/testfile"
with open(test_file, "w") as file:
    file.write(test_file)
    
print(delete_file(test_file)) # prints True
print(delete_file(test_file)) # prints False

This code imports os module and deletes a file, all within a lambda.

With some ingenuity, we can do loops in a lambda and execute conditionals. Consider this implementation of the Fizzbuzz game:

fizzbuzz = lambda max_value: (
        f15 := lambda n: "fizzbuzz" if n%15 == 0 else None,
        f5 := lambda n: "fizz" if n%3 == 0 else None,
        f3 := lambda n: "buzz" if n%5 == 0 else None,
        get_str := lambda n: f15(n) or f5(n) or f3(n) or str(n),
        loop := (print(get_str(n)) for n in range(1,max_value+1)), # deferred loop
        [x for x in loop if False] # execute the deferred loop, discarding all values
    )[-1]

fizzbuzz(15) # prints 1, 2, fizz, 4, buzz, ...

The only thing you can’t really do in a lambda is to catch exceptions. This would work if we had a built-in function similar to this:

def do(f):
    try:
        return f(), None
    except Exception as e:
        return None, e

Alas, there is no such function. contextlib.suppress() comes tantalizingly close, but it requires a with statement, and we can’t have with statements in a lambda either. Of course, we could define such function using exec(), but then we could define any function using exec() and the whole point of doing things in a lambda would be moot.

Why does it matter what we can run in a lambda? It has to do with safety of eval(), which we’ll discuss next. Or at least at some point in the future. I have a draft post about it that has been lying around for a few months, and I intend to publish it soon.

Leave a Reply

Your email address will not be published. Required fields are marked *