SchoolWhool
Quizzes and saved progress, freeSign in freeSign in

Python 4.4Free video11 minLesson 4 of 4 in Module 4

Lambdas and Functions as Values

Pass functions around, the pattern behind `sorted(key=...)` and `apply`.

Free sample. Everything in this module opens with a free account.

Watch or read the lesson: Lambdas and Functions as Values

01Sorting the menu

Start with something familiar. In the dictionaries lesson you created a small restaurant menu with three items: Pasta for $12.50, Salad for $8.25 and Soup for $6.00. Bring that dictionary back:

menu = {
    "Pasta": 12.50,
    "Salad": 8.25,
    "Soup": 6.00
}

Sorting by name

Suppose you want to sort the menu. You already know that Python has a built-in function called sorted(), so try passing the menu to it:

sorted(menu)
['Pasta', 'Salad', 'Soup']
Predict first

You get Pasta, Salad and Soup, in alphabetical order. Why did Python sort the item names instead of their prices? Think it through before reading on.

I have a guess. Show meHide the answer

Remember that when you loop over a dictionary, you get its keys by default. sorted() works the same way. It receives the dictionary's keys and sorts them alphabetically.

Sorting by price

But what if you don't want alphabetical order? What if you want to arrange the menu from the cheapest item to the most expensive? Then Soup should come first, then Salad, and finally Pasta. How would you do that?

Here is something interesting. You can write this:

sorted(menu, key=menu.get)
['Soup', 'Salad', 'Pasta']

Soup, Salad and Pasta: exactly the order you wanted.

What is being passed?

Look carefully at that code, especially this part: key=menu.get. You have seen the get() method before. You used it to look up values in a dictionary. But here there are no parentheses after get. The code passes menu.get itself.

So what exactly are you passing to sorted(), and how does Python know what to do with it? That is what this lesson explores. You will learn how functions can be treated as objects, how to pass one function to another, and how to create short functions using something called a lambda expression.

02Functions are objects

Start with something from the first functions lesson. When you define a function using def, Python creates a function object and binds the function's name to that object. So the function name is really just a name that refers to a function object. And just like other objects in Python, a function can have more than one name referring to it.

Two names for one function

Here is what that means. Remember the with_tax function? It takes a price and adds 8% tax by default. Use the version with a default tax rate:

def with_tax(price, rate=0.08):
    return round(price * (1 + rate), 2)

Now write:

tax = with_tax

Notice something important. There are no parentheses after with_tax, so you are not calling the function. Nothing is printed, because nothing was computed. Now call tax:

tax(12.50)
13.5

You get 13.5, exactly the same result as calling with_tax(12.50):

with_tax(12.50)
13.5
Predict first

Why do two different names give the same result? Think it through before reading on.

I have a guess. Show meHide the answer

Both names refer to the same function object. You can even check that with is:

tax is with_tax
True

Python returns True, because both names refer to the exact same object.

A reference or a call

Here is the key distinction. When you write with_tax without parentheses, you are referring to the function object. When you write with_tax(12.50), you are calling the function: Python executes its body and gives you the returned value. The parentheses are what turn a function reference into a call.

Dictionary methods work the same way. If you write menu.get without parentheses, you get a reference to the method bound to that dictionary:

menu.get
<function dict.get(key, default=None, /)>

That output describes the method object itself: the dictionary's get method, with the parameters it accepts. But if you write menu.get("Soup"), you are calling the method, and it returns the price of Soup:

menu.get("Soup")
6.0

So the important idea is that functions and methods can be passed around as values. You don't always have to call them immediately. Because Python treats functions like any other value, they are often called first-class functions. That opens up some interesting possibilities.

03Passing a function

For example, you can pass a function as an argument to another function. Here is a simple example. Suppose you have the three restaurant prices: 12.50, 8.25 and 6.00. You want to apply the same operation to every price. Maybe you want to add tax, or round each price, or perform some other calculation. Instead of writing a different loop for every operation, what if you write one function that accepts another function as an argument?

A function that takes a function

Call it apply_to_all. It takes two parameters. The first is func, which will refer to a function. The second is values, the collection you want to process. Inside, a list comprehension calls func on each value and returns the resulting list:

def apply_to_all(func, values):
    return [func(value) for value in values]

Now try it on the list of prices:

prices = [12.50, 8.25, 6.00]
apply_to_all(with_tax, prices)
[13.5, 8.91, 6.48]

You get 13.5, 8.91 and 6.48: exactly the prices after adding 8% tax.

Notice what happened. You passed with_tax as the first argument, just the function itself. Then, inside apply_to_all, Python called that function on each price. On the first iteration, it effectively called with_tax(12.50), then with_tax(8.25), and finally with_tax(6.00), and it collected the results into a new list.

Here is the interesting part. apply_to_all doesn't know anything about tax. It simply calls whichever function you give it, so you can reuse it for other operations.

Higher-order functions

A function that takes another function as an argument, or returns a function, is called a higher-order functionKey termA function that takes a function as an argument, or returns one: sorted with key=, map, filter.. apply_to_all is a higher-order function because it accepts another function as an argument. Python already has several built-in higher-order functions, and one of them is sorted().

The key parameter

That brings you back to the restaurant menu, and to how the key parameter works. When you call sorted() without a key function, Python compares the items directly. When you provide a key functionKey termA function passed as key=, whose result is what sorted, min and max compare., Python calls that function on each item to obtain the value used for sorting. So the key function decides what Python compares.

Remember the list of words from the tokenizer?

words = ['kettle', 'boils', 'fast', 'and', 'quiet']

Suppose you want to sort them from shortest to longest. Normally, sorted(words) would sort the strings alphabetically. But you want to sort by length, so you can pass the built-in len function as the key:

sorted(words, key=len)
['and', 'fast', 'boils', 'quiet', 'kettle']

You get a list sorted by the number of characters in each word. Notice that boils and quiet have the same length, five characters. boils appeared before quiet in the original list, so they remain in that order.

Now look at what sorted() actually did. It called len on each word and used the returned lengths to determine the order. Then it returned a new list containing the original words in that order. It didn't return the lengths themselves. It returned the words.

Why key=menu.get works

Now you can finally understand the expression from the beginning of this lesson:

sorted(menu, key=menu.get)
['Soup', 'Salad', 'Pasta']

The dictionary contains three keys: Pasta, Salad and Soup. When you pass the dictionary to sorted(), Python iterates over those keys. But this time you have provided menu.get as the key function, so Python effectively calls:

  • menu.get("Pasta"), which gives 12.50
  • menu.get("Salad"), which gives 8.25
  • menu.get("Soup"), which gives 6.00

So instead of comparing the item names, Python compares their prices, and arranges the original keys in ascending order of those prices. That gives Soup, Salad and Pasta, and that is why the expression works.

04Lambda expressions

So far, you have passed functions that already existed, like len, with_tax and menu.get. But what if you need a very small function for just one operation, something so simple that writing a full function definition feels unnecessary? Python gives you another way to create a function, using the keyword lambda.

Writing a lambda

Suppose you want a function that takes a price, adds 8% tax, and rounds the result to two decimal places. You could write it using def, just like with_tax. But you can also express it with a lambda expression, which creates a function from a single expression:

lambda price: round(price * 1.08, 2)
<function __main__.<lambda>(price)>

The output shows that Python has created a function object. It has no name of its own, so the notebook labels it <lambda>, followed by its parameter, price.

Read the expression from left to right:

  1. First, the keyword lambda.
  2. Then the parameter name, price.
  3. Then a colon.
  4. After the colon, the expression you want to evaluate.

So this lambda takes a price, calculates the price after adding 8% tax, rounds it to two decimal places, and returns that result.

No return, no name

Notice something important: there is no return statement. A lambda function automatically returns the value of its expression. There is no function name written in the expression either. That is why lambdas are often called anonymous functions: they are function objects created without defining a name using def.

Most of the time, you write a lambda directly where another function expects a function argument. You will see one used that way in the next section.

05Map and filter

There are two more built-in higher-order functions worth knowing, because you will see them quite often when reading Python code.

map()

The first is map(). Remember apply_to_all? It took another function and applied it to every value in a collection. map() does something very similar:

list(map(with_tax, prices))
[13.5, 8.91, 6.48]

You get the same results. Here is how the syntax works. You pass with_tax as the first argument to map, and prices as the second. map applies with_tax to each price as you iterate over the result, and the outer list() collects those results into a list.

Conceptually, this is very similar to the earlier list comprehension. Both produce the same list of taxed prices:

[with_tax(price) for price in prices]
[13.5, 8.91, 6.48]

filter()

The second function is filter(). As the name suggests, it filters values. But instead of writing an if condition directly in a comprehension, you pass a function that decides whether each value should be kept.

Remember the weekly step counts? You wanted to keep only the days when you walked at least 10,000 steps. With filter() and a lambda, you can write it like this:

week = [8200, 10450, 6300, 12000, 9100, 7400, 11050]
list(filter(lambda steps: steps >= 10000, week))
[10450, 12000, 11050]

Here is what is happening. filter calls the lambda on each step count. If the result is truthy, that step count is included. If the result is falsy, it is skipped. The outer list() collects the included values. So this is equivalent to the comprehension:

[s for s in week if s >= 10000]
[10450, 12000, 11050]

Iterators

Notice that both map and filter were wrapped in list(). That is because they don't give back a list. They give back an iteratorKey termAn object that gives its values one at a time, when asked for the next one. map and filter return iterators, which are used up after one pass., which produces the values one at a time. list() collects those values so you can see them.

Which style to use

So which approach should you use in your own code? map and filter are useful, and you will definitely encounter them in Python programs. But for simple transformations and filtering, a list comprehension is often easier to read. Understand both styles, and when a comprehension makes the code easier to follow, it is usually a good choice.

06Recap and practice

This lesson started by sorting the restaurant menu. You saw that sorted() normally compares the dictionary's keys, but by passing menu.get as the key function, you could sort those keys by their prices instead. Then you learned why that works.

Functions are objects in Python. You can assign them to variables, pass them as arguments and call them later. You also learned about higher-order functions: functions that accept other functions or return functions.

Then you explored lambda expressions, which let you create short functions directly inside another function call. After that came map and filter. map applies a function to values, and filter keeps values based on whether a function returns something truthy. Both return iterators.

Your practice

That brings you to the end of the functions module, which has covered quite a lot. Now take your time to:

  1. Work through all of the module's notebooks.
  2. Complete the assignments.
  3. Take the quizzes.

Try each task yourself before opening the separate solutions. The next module is about organizing your code into modules.