Course outline · 0% complete

0/27 lessons0%

Course overview →

Lambdas, sorted key=, and higher-order functions

lesson 2-3 · ~14 min · 6/27

Functions are values

Here is the idea this whole lesson builds on: in Python, a function is a value, just like a number or a string. You can store it in a variable, put it in a list, or pass it to another function. A function that takes or returns another function is called a higher-order function.

def shout(text):
    return text.upper() + "!"

say = shout          # no parentheses: we pass the function itself
print(say("hi"))     # HI!

Writing shout hands over the function. Writing shout(...) calls it. That distinction matters everywhere below.

The payoff is immediate and practical: custom sort orders, leaderboards by score, files by size, orders by date, are all one key= away once you can hand one function to another.

sorted(key=...)

You used sorted(nums) in Python for Beginners. Its superpower is the key parameter: pass a function, and Python calls it on each item to decide the sort order, without changing the items:

words = ["pear", "fig", "banana"]
sorted(words, key=len)   # ['fig', 'pear', 'banana']

When the key is tiny and used once, defining a whole def is heavy. A lambda is a one-expression, inline function:

lambda w: w[-1]      # takes w, returns its last character

lambda arguments: expression and nothing more. No statements, no return keyword, the expression is the return value.

Sorting names case-insensitively

In the default string order every capital letter sorts before every lowercase letter, which is rarely what a person reading a list expects. Passing key=str.lower sorts by a lowercased shadow of each string while still returning the original strings untouched.

names = ["banana", "Apple", "Cherry"]

print(sorted(names))
print(sorted(names, key=str.lower))

Output

['Apple', 'Cherry', 'banana']
['Apple', 'banana', 'Cherry']

The key function never changes the data. It only computes the value each item is compared by, which is why the second line still shows Apple and Cherry with their original capitals.

Three orders from one list of tuples

The same list can come out in three different orders, chosen purely by the key function and the reverse flag.

players = [("mia", 320), ("leo", 480), ("zoe", 150)]

print(sorted(players))
print(sorted(players, key=lambda p: p[1]))
print(sorted(players, key=lambda p: p[1], reverse=True))

Output

[('leo', 480), ('mia', 320), ('zoe', 150)]
[('zoe', 150), ('mia', 320), ('leo', 480)]
[('leo', 480), ('mia', 320), ('zoe', 150)]

With no key at all, Python compares tuples element by element, so the names decide the order alphabetically. key=lambda p: p[1] pulls out the score instead, giving lowest first, and adding reverse=True flips that to highest first. The first and third lines match here only by coincidence: alphabetical order happens to agree with descending score for this data.

Where you will meet this again

  • max(items, key=...) and min(items, key=...) accept the same key idea.
  • sorted(d.items(), key=lambda kv: kv[1]) sorts a dict by value, a pattern you will use in the Counter lesson (6-1) and the capstone (unit 10).
  • Comprehensions from unit 1 usually beat map and filter, but you should recognize map(f, xs) and filter(f, xs) when reading other people's code: they apply or test f one item at a time as you loop over the result, instead of all at once up front (unit 4 covers this on-demand style in depth).

One caution from the style police: if a lambda gets hard to read, promote it to a named def. Lambdas are for tiny keys, not logic.

Sorting a dict by its values

A dict has no useful order of its own, so reports built from one almost always go through sorted. Calling sorted on inventory.items() with a lambda key gives the pairs back smallest count first.

inventory = {"nails": 130, "screws": 74, "bolts": 210}

for name, count in sorted(inventory.items(), key=lambda kv: kv[1]):
    print(name, count)

Output

screws 74
nails 130
bolts 210

inventory.items() yields (name, count) pairs, and the key function lambda kv: kv[1] reaches into each pair to pull out the count. Because sorted hands back the whole pairs, the for line can unpack them straight into name and count.

Finding the top scorer with max

max accepts the same key= argument as sorted, so finding a single winner does not require sorting the whole list first. One call compares by score and returns the entire winning tuple.

players = [("mia", 320), ("leo", 480), ("zoe", 150)]

top = max(players, key=lambda p: p[1])
print(top[0], top[1])

Output

leo 480

max(players, key=lambda p: p[1]) compares by score but hands back the whole tuple, not just the number, so indexing or unpacking it recovers the name alongside the score. This is also the cheaper move: max makes a single O(n) pass, while sorting to grab the first item costs O(n log n).

The key decides order, not the result

sorted(["bb", "a", "ccc"], key=len) returns ['a', 'bb', 'ccc']. The key function len is called on each string, producing 1, 2, and 3, and the strings are placed in the order those numbers imply.

The important detail is that the items come back, never the key values. sorted uses the key only for comparison and then returns the original objects, so a result of [1, 2, 3] would be a misreading of what a key function does.