Functions as First-Class Citizens
Intermediate · 20 min read · ▶ live playground · ✦ checkpoint
You already know how to call a function. The next step is stranger: in Python, a function is also a value you can store, pass around, and return from another function.
Functions are objects too
A first-class object is a value the language lets you treat like any other value: bind it to a name, put it in a collection, pass it as an argument, or return it. Python functions are first-class objects.
Think back to the recipe-card analogy from 7.1. Calling a function is using the recipe. Treating the function as an object is handling the card itself: you can put it in a binder, hand it to someone else, or choose which card to use later.
def cheer(player):
return f"Go, {player}!"
def whisper(player):
return f"You've got this, {player}."
message_maker = cheer
messages = [cheer, whisper]
print(message_maker("Mina"))
for make_message in messages:
print(make_message("Ravi"))Go, Mina!Go, Ravi!You've got this, Ravi.
Notice the missing parentheses in message_maker = cheer. That line does not call cheer; it stores the function object under another name. Parentheses mean "run it now." No parentheses means "refer to the function itself."
Pass behavior as an argument
A higher-order function is a function that takes another function as an argument, returns a function, or both. This lets one function control the shape of the work while another function supplies the behavior.
def loud_message(player):
return f"{player.upper()} is ready"
def calm_message(player):
return f"{player} is steady"
def announce_players(players, make_message):
for player in players:
print(make_message(player))
team = ["Mina", "Ravi", "Noor"]
announce_players(team, loud_message)
announce_players(team, calm_message)MINA is readyRAVI is readyNOOR is readyMina is steadyRavi is steadyNoor is steady
announce_players() does not care how a message is made. It only knows that make_message is something it can call with a player name. That is the useful part: the loop lives in one place, while the behavior can change.
This pattern appears everywhere in Python. You give a function a small bit of behavior, and it applies that behavior to data.
Return a function
Functions can also create and return other functions. The outer function builds the recipe card. The returned function is the card you use later.
def make_bonus_adder(bonus):
def add_bonus(score):
return score + bonus
return add_bonus
small_bonus = make_bonus_adder(5)
big_bonus = make_bonus_adder(20)
print(small_bonus(40))
print(big_bonus(40))4560
make_bonus_adder(5) returns a function. That returned function remembers the bonus value from the enclosing scope you studied in 7.3. You do not need to use this pattern constantly yet. You do need to recognize it: a function call can hand you back a function object, not just a number or string.
Use lambda for tiny functions
A lambda function is a small anonymous function, which means a function with no def name of its own, written as an expression with lambda.
players = ["Mina", "Ravi", "Noor"]
scores = [42, 35, 48]
shout = lambda player: player.upper()
print(shout("Mina"))
print(list(map(lambda score: score + 10, scores)))
print(list(map(lambda player: f"Player: {player}", players)))MINA[52, 45, 58]['Player: Mina', 'Player: Ravi', 'Player: Noor']
The shape is:
lambda player: player.upper()means "takeplayer, returnplayer.upper()."- The part before
:names the parameter. - The part after
:is the returned expression.
Use lambda when the function is tiny and used right where it is written. If the logic needs a real name, multiple steps, or a docstring, use def. Clear beats short.
First look at map, filter, and sorted
map() is a built-in function that applies one function to each item in an iterable. filter() is a built-in function that keeps only items where a function returns a truthy value. Their results are lazy, which means Python waits to produce each value until something asks for it, so wrap them in list() when you want to see all the values now.
scores = [42, 35, 48, 29]
players = [
{"name": "Mina", "score": 42},
{"name": "Ravi", "score": 35},
{"name": "Noor", "score": 48},
]
def add_curve(score):
return score + 5
def is_passing(score):
return score >= 40
print(list(map(add_curve, scores)))
print(list(filter(is_passing, scores)))
print(sorted(players, key=lambda player: player["score"]))[47, 40, 53, 34][42, 48][{'name': 'Ravi', 'score': 35}, {'name': 'Mina', 'score': 42}, {'name': 'Noor', 'score': 48}]
sorted() already returns a list. Its key argument accepts a function that tells Python what to sort by. In key=lambda player: player["score"], Python calls that tiny function for each dictionary and sorts by the returned score.
You can often write the same idea with a loop or a list comprehension. That is fine. These tools are worth knowing because Python code often says "apply this function to each item" or "sort by this calculated value."
Try it - choose the behavior
Run this, then change mode to "quiet" or "score". You are not changing the loop that prints the roster; you are changing which function the loop uses.
This is the whole first-class function idea in one small program. choose_labeler() returns a function. make_label stores that function. sorted() receives a tiny lambda for its key, and map() plus filter() receive tiny functions for transforming and keeping players.
Checkpoint
Answer all three to mark this lesson complete
Functions are now more than reusable blocks; they are values your program can choose, store, and pass around. Next, type hints give functions small labels for expected values and returned values without changing how Python runs them.