More functions
Chapter 5 covered the basics. This chapter shows the features that make Python functions flexible: any number of arguments, functions passed around like values, and functions that call themselves.
Any number of arguments
A parameter written as *args collects any extra positional arguments into a tuple:
def total(*numbers):
result = 0
for n in numbers:
result += n
return result
total(1, 2) # 3
total(1, 2, 3, 4) # 10
total() # 0
**kwargs does the same for keyword arguments, collecting them into a dictionary:
def describe(**info):
for key, value in info.items():
print(f"{key} = {value}")
describe(name="Ada", year=2)
# name = Ada
# year = 2
The names args and kwargs are just conventions; the * and ** are what matter.
Unpacking when you call
The stars also work the other way, spreading a list or dictionary out into separate arguments:
point = [3, 4]
print(*point) # same as print(3, 4)
options = {"sep": "-", "end": "!\n"}
print("a", "b", **options) # a-b!
Keyword-only arguments
Parameters after a bare * must be passed by name. This makes calls readable and stops arguments being mixed up:
def connect(host, *, port=443, timeout=10):
return f"{host}:{port} (timeout {timeout}s)"
connect("example.com", port=8080) # fine
connect("example.com", 8080) # TypeError
Functions are values
A function can be stored in a variable, put in a list or passed to another function, just like a number or a string:
def shout(text):
return text.upper() + "!"
def whisper(text):
return text.lower() + "..."
def apply(style, text):
return style(text)
apply(shout, "hello") # 'HELLO!'
apply(whisper, "HELLO") # 'hello...'
Notice there are no brackets after shout when passing it in. shout is the function itself; shout("hi") calls it.
Lambdas and sorting with a key
A lambda is a small function written in one expression, handy when you need a function for a single use. The most common place you will meet one is the key argument of sorted, min and max:
words = ["banana", "kiwi", "apple", "fig"]
sorted(words) # alphabetical
sorted(words, key=len) # ['fig', 'kiwi', 'apple', 'banana']
sorted(words, key=lambda w: w[-1]) # by last letter
students = [("Ada", 88), ("Linus", 95), ("Grace", 91)]
max(students, key=lambda s: s[1]) # ('Linus', 95)
key is called once on every item, and items are compared by what it returns. If a lambda gets longer than one short line, use a normal def instead.
Recursion
A recursive function calls itself on a smaller version of the problem. Every recursive function needs a base case that stops it:
def factorial(n):
if n <= 1: # base case
return 1
return n * factorial(n - 1)
factorial(5) # 120
Recursion fits naturally when the data is itself nested, such as folders inside folders or trees. Python stops recursion after about 1,000 levels with a RecursionError, so for simple counting a loop is usually better.
Type hints
You can note the types a function expects and returns. Python does not enforce them, but editors and tools like mypy use them to catch mistakes, and they document your code:
def average(scores: list[float]) -> float:
return sum(scores) / len(scores)
Exercises
1. Average of anything
Write average(*nums) that returns the average of any number of arguments, or None if there are none.
Show answer
def average(*nums):
if not nums:
return None
return sum(nums) / len(nums)
average(2, 4, 9) # 5.0
average() # None
2. Sort by two things
Sort ["pear", "fig", "apple", "kiwi", "date"] by length, and alphabetically when two words have the same length.
Show answer
words = ["pear", "fig", "apple", "kiwi", "date"]
sorted(words, key=lambda w: (len(w), w))
# ['fig', 'date', 'kiwi', 'pear', 'apple']
Tuples compare item by item, so the key sorts by length first and uses the word itself to break ties.
3. Recursive sum of digits
Write digit_sum(n) recursively for a non-negative integer, without converting it to a string.
Show answer
def digit_sum(n):
if n < 10:
return n
return n % 10 + digit_sum(n // 10)
digit_sum(4096) # 19
The base case is a single digit. Each call handles the last digit and passes the rest on.