11.2. Functional Lambda

  • Lambda - Anonymous functions

  • When function is used once

  • When function is short

  • You don't need to name it (therefore anonymous)

In Python, a lambda expression is a small anonymous function that can have any number of arguments, but can only have one expression. It is also known as a lambda function or lambda form.

Lambda expressions are defined using the lambda keyword, followed by the function's arguments and a colon, and then the expression to be evaluated. The result of the expression is returned automatically.

Here's an example of a lambda expression that adds two numbers:

>>> add = lambda x, y: x + y
>>>
>>> result = add(2, 3)
>>>
>>> print(result)
5

In this example, the lambda keyword is used to define a function that takes two arguments (x and y) and returns their sum. The function is assigned to the variable add. The add() function is then called with the arguments 2 and 3, and the result is stored in the variable result.

Lambda expressions are often used as a shortcut for defining small, one-off functions that are only needed in a specific context. They can be used anywhere that a function is expected, such as in the map() and filter() functions.

lambda

Anonymous function

>>> lambda x: x+1  
<function <lambda> at 0x...>

Lambda Expressions:

>>> a = lambda x: x+1
>>> b = lambda x,y: x+y

Equivalent functions:

>>> def a(x):
...     return x+1
>>> def b(x,y):
...     return x+y

11.2.1. Syntax

lambda <arguments>: <expression>

11.2.2. Convention

  • Usually parameters are named x and y

  • Use shortest code possible

  • Do not assign lambda to variable

  • Lambda is anonymous function and it should stay anonymous. Do not name it

  • PEP 8 -- Style Guide for Python Code: "Always use a def statement instead of an assignment statement that binds a lambda expression directly to an identifier". Lambda is anonymous function and it should stay anonymous. Do not name it.

  • Usually there are no spaces in lambda expressions (to make code shorter)

Bad:

>>> square = lambda x: x**2
>>> square(4)
16

Good:

>>> def square(x):
...     return x**2
...
>>> square(4)
16

PEP 8 -- Style Guide for Python Code: "Always use a def statement instead of an assignment statement that binds a lambda expression directly to an identifier":

11.2.3. Note to Programmers of Different Languages

query = 'SELECT * FROM users'

result = database(query).stream()
                        .filter(user -> user.age > 5)
                        .filter(user -> user.firstname == 'Mark')
                        .filter((x,y) -> x + y)
                        .collect(Collectors.toList());

11.2.4. Noop

>>> noop = lambda: ...
>>> def request(on_error = lambda: ...):
...     ...

11.2.5. Lambda with Map

Increment:

>>> data = [1, 2, 3, 4]
>>>
>>> result = map(lambda x: x+1, data)
>>> list(result)
[2, 3, 4, 5]

11.2.6. Lambda with Filter

Even numbers:

>>> DATA = [1, 2, 3, 4]
>>>
>>> result = filter(lambda x: x%2==0, DATA)
>>> list(result)
[2, 4]

11.2.7. Performance

  • Python 3.11.5

>>> %%timeit -r 1000 -n 10_000  
... def increment(x):
...     return x + 1
... map(increment, range(0,100))
271 ns ± 30.6 ns per loop (mean ± std. dev. of 1000 runs, 10000 loops each)
>>> %%timeit -r 1000 -n 10_000  
... map(lambda x: x+1, range(0,100))
262 ns ± 29 ns per loop (mean ± std. dev. of 1000 runs, 10000 loops each)
>>> %%timeit -r 1000 -n 1000  
... def increment(x):
...     return x + 1
... list(map(increment, range(0,100)))
7.48 µs ± 608 ns per loop (mean ± std. dev. of 1000 runs, 1000 loops each)
>>> %%timeit -r 1000 -n 1000  
... list(map(lambda x: x+1, range(0,100)))
7.36 µs ± 545 ns per loop (mean ± std. dev. of 1000 runs, 1000 loops each)

11.2.8. Advanced With Args and Kwargs

>>> total = lambda a,b,*args,**kwargs: a+b+sum(args)+sum(kwargs.values())
>>> total(1,2,3,4,5,6,x=10,y=20)
51

11.2.9. Use Case - 0x01

>>> data = [1, 2, 3, 4]
>>>
>>> result = map(lambda x: x**2, data)
>>> list(result)
[1, 4, 9, 16]

11.2.10. Use Case - 0x02

>>> PL = {'ą': 'a', 'ć': 'c', 'ę': 'e',
...       'ł': 'l', 'ń': 'n', 'ó': 'o',
...       'ś': 's', 'ż': 'z', 'ź': 'z'}
>>>
>>> text = 'zażółć gęślą jaźń'
>>>
>>>
>>> result = map(lambda x: PL.get(x,x), text)
>>> ''.join(result)
'zazolc gesla jazn'

11.2.11. Use Case - 0x03

>>> people = [
...     {'age': 21, 'name': 'Mark Watney'},
...     {'age': 25, 'name': 'Melissa Lewis'},
...     {'age': 18, 'name': 'Rick Martinez'},
... ]
>>>
>>>
>>> result = filter(lambda x: x['age'] >= 21, people)
>>> list(result)  
[{'age': 21, 'name': 'Mark Watney'},
 {'age': 25, 'name': 'Melissa Lewis'}]

11.2.12. Use Case - 0x04

>>> people = [
...     {'is_staff': True, 'name': 'Mark Watney'},
...     {'is_staff': False, 'name': 'Melissa Lewis'},
...     {'is_staff': True, 'name': 'Rick Martinez'},
... ]
>>>
>>>
>>> can_login = filter(lambda x: x['is_staff'], people)
>>> list(can_login)  
[{'is_staff': True, 'name': 'Mark Watney'},
 {'is_staff': True, 'name': 'Rick Martinez'}]

11.2.13. Use Case - 0x05

>>> users = [
...     'mwatney',
...     'mlewis',
...     'rmartinez',
...     'avogel',
...     'bjohanssen',
...     'cbeck',
... ]
>>>
>>> staff = [
...     'mwatney',
...     'mlewis',
...     'ptwardowski',
...     'jjimenez',
... ]
>>>
>>>
>>> can_login = filter(staff.__contains__, users)
>>> list(can_login)
['mwatney', 'mlewis']

11.2.14. Use Case - 0x06

>>> from urllib.request import urlopen
>>>
>>> def fetch(url: str,
...           on_success = lambda result: ...,
...           on_error = lambda error: ...,
...           ) -> None:
...     try:
...         result = urlopen(url).read()
...     except Exception as error:
...         return on_error(error)
...     else:
...         return on_success(result)
>>>
>>> 
... fetch(
...     url = 'https://python3.info',
...     on_success = lambda result: print(result),
...     on_error = lambda error: print(error))

11.2.15. Use Case - 0x07

>>> class Apply:
...     def __init__(self, values):
...         self.values = values
...
...     def filter(self, fn):
...          self.values = filter(fn, self.values)
...          return self
...
...     def map(self, fn):
...         self.values = map(fn, self.values)
...         return self
>>> DATA = [1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>
>>> result = (
...     Apply(DATA)
...     .filter(lambda x: x % 2 == 0)
...     .map(lambda x: x ** 2)
...     .map(lambda x: x + 1)
...     .map(lambda x: x + 10)
... )
>>> list(result.values)
[15, 27, 47, 75]

11.2.16. Further Reading

11.2.17. Assignments

# FIXME: zmienić by nie było inlajnowania
"""
* Assignment: Functional Lambda Chain
* Type: class assignment
* Complexity: easy
* Lines of code: 2 lines
* Time: 3 min

English:
    1. Inline functions `odd()` and `cube()` with `lambda` expressions
    2. Run doctests - all must succeed

Polish:
    1. Zastąp funkcje `odd()` i `cube()` wyrażeniami `lambda`
    2. Uruchom doctesty - wszystkie muszą się powieść

Hints:
    * `mean = sum(...) / len(...)`
    * type cast to `list()` before calculating mean to expand generator

Tests:
    >>> import sys; sys.tracebacklimit = 0

    >>> type(result) is float
    True
    >>> result
    245.0
"""


def odd(x):
    return x % 2


def cube(x):
    return x ** 3


# Inline lambda expressions
# type: float
result = range(0,10)
result = filter(odd, result)
result = map(cube, result)
result = list(result)
result = sum(result) / len(result)