Unit 16: Functions as Things
Passing functions around.
Unit 16 of 31 in Python for kids. Its 7 lessons are Functions Are Values Too, Tiny Functions, Map and Filter, As Many As You Like, Functions That Build Functions, Decorators and Function Wizard — below is everything each one explains, and a question or two from it to try.
Every sample on this page was run through real Python before it shipped, and prints exactly what it says it prints.
This unit opens with a fortnight’s trial of everything — no card needed — or with a family plan, bought in the iPhone app. The first two units of every track are free for ever. Try it in the app.
📦 Functions Are Values Too
A function without its brackets
Write shout() and it runs. Write shout on its own and you get the function itself — which you can put in a variable, exactly like a number.
Python
def shout(word):
return word.upper()
noisy = shout
print(noisy("hello"))
It prints
HELLO
Handing a function to a function
If a function is a value, you can pass one in as an argument. Now apply can do *anything* — it depends entirely on what you give it.
Python
def shout(word):
return word.upper()
def whisper(word):
return word.lower()
def apply(fn, word):
return fn(word)
print(apply(shout, "Hi"))
print(apply(whisper, "Hi"))
It prints
HI hi
This is what key= has been doing
You have used this already. sorted takes a function and calls it on each item to decide what to sort by.
Python
def length(word):
return len(word)
words = ["pear", "fig", "banana"]
print(sorted(words, key=length))
It prints
['fig', 'pear', 'banana']
Try it yourself
What is the difference between shout and shout("hi")?
- Nothing
- The first is the function itself; the second runs it and gives the answer
- The first is a mistake
- The second is faster
What does this print?
Python
def double(n):
return n * 2
def run_twice(fn, n):
return fn(fn(n))
print(run_twice(double, 3))
Answer them in the app
🐜 Tiny Functions
A function with no name
When a function is one short line and you only need it once, lambda writes it in place. There is no def, no name and no return — the answer is just the bit after the colon.
Python
double = lambda n: n * 2
print(double(5))
It prints
10
The same function, both ways
These two are exactly the same thing. lambda is only ever a shorter way of writing a very small def.
Python
def add_a(x, y):
return x + y
add_b = lambda x, y: x + y
print(add_a(2, 3))
print(add_b(2, 3))
It prints
5 5
Where you actually meet it
This is the key=lambda you have seen before. It says "when sorting, look at the age" — too small a job to be worth a whole def.
Python
kids = [("Ada", 11), ("Sam", 7)]
for kid in sorted(kids, key=lambda pair: pair[1]):
print(kid[0])
It prints
Sam Ada
Try it yourself
What does this print?
Python
square = lambda n: n * n
print(square(6))
When should you NOT use a lambda?
- Never, lambdas are always better
- When the function is more than a line, or you want to give it a helpful name
- When it takes two arguments
- When it returns a number
Answer them in the app
🗺️ Map and Filter
Do the same thing to everything
map runs a function on every item and gives back the results. Wrap it in list() to see them.
Python
nums = [1, 2, 3]
print(list(map(lambda n: n * 2, nums)))
It prints
[2, 4, 6]
Keep only the ones you want
filter keeps an item when the function says True about it, and drops it otherwise.
Python
nums = [1, 2, 3, 4, 5]
print(list(filter(lambda n: n > 3, nums)))
It prints
[4, 5]
Squashing a list to one answer
reduce folds a list down to a single value, two at a time. It lives in functools, and honestly sum() is nicer when it will do.
Python
from functools import reduce
print(reduce(lambda a, b: a + b, [1, 2, 3, 4]))
print(sum([1, 2, 3, 4]))
It prints
10 10
Try it yourself
What is the difference between map and filter?
- map changes every item; filter throws some items away
- They are the same
- map is faster
- filter changes items too
What does this print?
Python
words = ["a", "bb", "ccc"]
print(list(map(len, words)))
Answer them in the app
🎁 As Many As You Like
A function that takes any number of things
A * before a parameter collects however many arguments were passed into a tuple. The name args is just tradition — the * does the work.
Python
def total(*nums):
return sum(nums)
print(total(1, 2))
print(total(1, 2, 3, 4))
print(total())
It prints
3 10 0
Named extras with two stars
**kwargs collects any *named* arguments into a dictionary.
Python
def describe(**facts):
for key, value in facts.items():
print(key, "=", value)
describe(name="Ada", age=9)
It prints
name = Ada age = 9
Ordinary arguments can come first
You can have normal parameters and then *args to catch anything else.
Python
def greet(greeting, *names):
for name in names:
print(greeting, name)
greet("Hi", "Ada", "Sam")
It prints
Hi Ada Hi Sam
Try it yourself
What does this print?
Python
def count_them(*things):
print(len(things))
count_them("a", "b", "c")
count_them()
What does *nums give you inside the function?
- A single number
- A tuple holding all the arguments that were passed
- A dictionary
- The number of arguments
Answer them in the app
🏗️ Functions That Build Functions
A function defined inside another
A function can define another one inside itself, and hand it back. The inner one remembers the values it grew up with.
Python
def make_adder(n):
def add(m):
return n + m
return add
add5 = make_adder(5)
print(add5(3))
print(add5(10))
It prints
8 15
A factory for functions
Each call makes a *different* function, with its own remembered value.
Python
def times(n):
return lambda m: n * m
double = times(2)
triple = times(3)
print(double(5))
print(triple(5))
It prints
10 15
Try it yourself
How does add still know what n was, after make_adder has finished?
- It does not, it guesses
- The inner function keeps hold of the values around it when it was made
- n is global
- Python re-runs make_adder each time
What does this print?
Python
def power(exp):
def go(n):
return n ** exp
return go
square = power(2)
cube = power(3)
print(square(4))
print(cube(2))
Answer them in the app
🎀 Decorators
Wrapping a function in another
A decorator takes a function and gives back a new one that does a bit more. You have already used @property and @classmethod — this is what that @ means.
Python
def loud(fn):
def wrapper():
print("--- start ---")
fn()
print("--- end ---")
return wrapper
@loud
def hello():
print("hello")
hello()
It prints
--- start --- hello --- end ---
What the @ is short for
These two do exactly the same thing. The @ is only a tidier way of saying "run my function through this one and keep the result".
Python
def loud(fn):
def wrapper():
print("hi!")
fn()
return wrapper
def bye():
print("bye")
bye = loud(bye)
bye()
It prints
hi! bye
Letting arguments through
If the wrapped function takes arguments, the wrapper must pass them along — which is exactly what *args and **kwargs are for.
Python
def twice(fn):
def wrapper(*args, **kwargs):
fn(*args, **kwargs)
fn(*args, **kwargs)
return wrapper
@twice
def greet(name):
print("Hi " + name)
greet("Ada")
It prints
Hi Ada Hi Ada
Try it yourself
What does a decorator take, and give back?
- A number, and a number
- A function, and a new function
- A class, and an object
- Nothing, and nothing
What does this print?
Python
def shout(fn):
def wrapper(word):
return fn(word).upper()
return wrapper
@shout
def say(word):
return "i said " + word
print(say("hi"))
Answer them in the app
🏆 Function Wizard
Try it yourself
What does this print?
Python
add = lambda a, b: a + b
print(list(map(lambda n: add(n, 10), [1, 2])))
Answer it in the app