A snake with no name – Lambdas in Python (also functions and list comprehensions)

Sweet and short: Instead of a function you can write a single statement in Python as a lambda. Either is one either better or worse nor does one replace the other. They both have their place and intended use.

Functions hopefully have describing names, are probably used multiple times and handle a specific part which makes sense to group it into a function.
Lambdas on the other hand don’t have a name – they just represent a single statement and therefore a behavior. pep8 also reminds us not to assign a lambda to a variable. That should also show the intended volatile nature of lambdas in the code.

This is still pretty abstract – so let’s dive into examples.

# function definition
def add_two(number:int) -> int:
    return number + 2

# the same as lambda
lambda number: number + 2

A function can just be called with function_name(parameter) – a lambda will most probably be a parameter in a function and can be called by appending parentheses containing the expected parameters.
Let’s see how this looks in the code and hop into some examples:

# function that takes a String and a procedure behavior in a lambda 
# and applies it to the text, prints out both and returns it
def process_text(text:str, procedure) -> str:
    print(f'The following text is processed: {text}')
    processed_text = procedure(text)
    print(f'The text now look like this: {processed_text}')
    return processed_text

# now we can call the function with the lambdas of our choice
process_text('And now 2 something...', lambda t: t.upper()) # to upper case

process_text('Python', lambda t: f'{t} is awesome') # appends ' is awesome'

process_text('StringFromObject', lambda t: type(t)) # returns type

process_text('XFghz', lambda t: 42) # discards the text and returns 42

# calls function car() with 'Porsche' and 911 as parameters and 
# returns the value it receives from it
process_text('Porsche', lambda t: car(t, 911)) 

Input and output are optional – you can have lambdas without or with multiple of them.

# without input - just returns 42
lambda : 42

# without output (None type)
# the built-in function print returns the None type
lambda x : print(x)

# with multiple input parameters
lambda x, y, z: (x+y)*z

# with multiple return values (in a tuple)
# if returning multiple values in a lambda, you have to put the parentheses
# explicitly around them - in a function you don't have to do this as it is
# done implicitly. As the lambda is a one-liner, the interpreter doesn't
# understand it without the parentheses.
lambda x: (type(x), x)

Where are lambdas used?
It really depends. They offer a nice, easy and volatile way to transfer behavior. You don’t have to use them to code in Python, but they really can be helpful and save you some function definitions for behavior you only need at this point.
Some built-in functions also support lambdas, like map() and filter(). With map() you apply a behavior to the current variable and probably change its value or type. The behavior the filter gets as parameter decides if a variable is kept in the current iterable or gets removed – depending if it matches the expression. Obviously filter lambdas have to return boolean values.

# map() example
my_list = list(map(lambda i: i+2, [1,2,3,4,5]))
print(my_list)
# will print out the changed list: [3,4,5,6,7]

# filter() example
my_list = list(filter(lambda i: i<= 5), [-2, 0, 5, 10])
print(my_list)
# will print out the filtered list: [-2, 0, 5]

This wouldn’t be complete if list comrehensions weren’t mentioned. These offer a very readable way of mapping and filtering values in iterables. As many use cases of map() and filter() using lambdas or functions can be solved with them I just had to mention them here. The followoing examples are the same as the above with map() and filter().

A list comprehension is a loop over an iterable with an expression what to do with the current value and an optional filter expression which has to match or the current value ist skipped and will not be included in the result list.

# Mapping the value in the do_stuff_with() function.
my_new_list = [do_stuff_with(value) for value in my_list]

# Filtering values depending on the return value of the matches() function.
my_new_list = [value for value in my_list if matches(value)]

# Mapping and filtering combined.
my_new_list = [do_stuff_with(value) for value in my_list if matches(value)] 
# mapping in a list comprehension
my_list = [i+2 for i in [1,2,3,4,5]]
print(my_list)
# will print out the changed list: [3,4,5,6,7]

# filtering in a list comprehension
my_list = [i for i in [-2, 0, 5, 10] if i<= 5]
print(my_list)
# will print out the filtered list: [-2, 0, 5]

A useful combination of list comprehension could be a function that expects the list and optionally a lambda for the mapping expression and one for the matching expression:

"""
The list parameter of the function is obligatory, but the mapper and the
matcher are optional as they have default values. If not provided the
default mapper always returns itself and the value is unchanged. If a mapper
is provided in the function call, it is used instead of the default one. The 
same for the matcher. If non´t provided, the default one filters values
below 0. If we provide our own matcher, it is used instead. The list
comprehension iterates over the provided list values and applies the mapper
to the current value them if the matcher expression returns True.
"""
def process_list(lst:[], mapper=lambda x: x, matcher=lambda x: x>=0) -> []:
    return [mapper(i) for i in lst if matcher(i)]

"""
This will print out the result list. As the default matcher filters 
negative values, -5 is discarded. Then the result list would be[2,4,6] as
the default mapper was used, which doesn't change the value.
"""
print(process_list([-5,2,4,6]))

"""
Our result list would be [25,81]. Values <=1 were filtered by the matcher
and the mapper was applied to the remaining values 5 and 9 and squared them.
"""
print(process_list([-2,1,5,9], mapper=lambda i: i*2, matcher=lambda i: i>1))

In comparision what would be needed if written with functions instead of lambdas. This will bloat up the code with functions and show how useful lambdas can be as don’t need the functions – lambdas would be just fine and save many lines of code.

# First we have to declare the functions to be able to use them.
def dflt_mapper(x:int) -> int:
    return x

def dflt_matcher(x:int) -> bool:
    return x >= 0

"""
Same process_list() as before, but with functions as mapper and matcher as 
well as in the call. Notice that functions as paramaters are referenced by
their name without the parentheses. They are added in the function code when
they are called. Same as before with the lambdas.
"""
def process_list(lst:[], mapper=dflt_mapper, matcher=dflt_matcher) -> []:
    return [mapper(i) for i in lst if matcher(i)]

"""
This will print out the result list. Same as before.
"""
print(process_list([-5,2,4,6]))

"""
To be able to call the function with custom behaviour, but without using
lambdas, we have to declare the functions we will be using.
"""
def my_mapper(x:int) -> int:
    return x * 2

def my_matcher(x:int) -> bool:
    return x > 1

"""
Our result list would be [25,81]. Same as in the code block before.
"""
print(process_list([-2,1,5,9], mapper=my_mapper, matcher=my_matcher))

Similar Posts

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.