What does it mean for functions to be first-class objects in Python?
First-class functions can be stored in variables, passed as arguments, returned from other functions, and placed in data structures — just like any other object. This is the foundation for higher-order functions, decorators, callbacks, and functional programming patterns in Python.
How to think about it
“First-class” just means functions are values — full objects you can pass around like an integer or a string. In Python every function is an instance of the function type, carrying its own attributes (__name__, __doc__, __closure__). Once that clicks, decorators, callbacks, and the functional patterns stop looking like special features and start looking like ordinary object-passing.
Concretely, there are four things you can do with a function because it’s an object: store it in a variable, drop it into a data structure, pass it as an argument, and return it from another function.
A worked example
from functools import partial
def square(x):
return x * x
# 1. Store in a variable — it's just a reference to the same object
op = square
print("op(4) =", op(4))
# 2. Store in a data structure — a dispatch table beats a long if/elif
ops = {"sq": square, "abs": abs, "neg": lambda x: -x}
print("ops['sq'](5) =", ops["sq"](5))
# 3. Pass as an argument — a higher-order function
def apply(func, values):
return [func(v) for v in values]
print("apply square :", apply(square, [1, 2, 3, 4]))
# 4. Return from a function — a factory / closure
def make_power(exp):
def power(x):
return x ** exp
return power
cube = make_power(3)
print("cube(4) =", cube(4))
# Fix some arguments and get a new callable back
double = partial(lambda factor, x: factor * x, 2)
print("double(7) =", double(7))
# And functions really are objects, with attributes and a type
print("square name :", square.__name__)
print("square type :", type(square))
op(4) = 16
ops['sq'](5) = 25
apply square : [1, 4, 9, 16]
cube(4) = 64
double(7) = 14
square name : square
square type : <class 'function'>
Each line is the same idea wearing a different hat. op = square copied a reference, not the code; the ops dict turned a branchy if/elif into a clean lookup; apply took behaviour as a parameter; make_power returned a freshly built function; and partial pre-filled an argument to make a new one. The last two lines are the punchline — type(square) really is <class 'function'>, an object like any other.
Patterns this unlocks
sorted(items, key=func)— hand the sort its criterion as a function.functools.partial— freeze some arguments, get back a smaller callable.- Dependency injection — pass behaviour in rather than inheriting it.
- Dispatch tables — a dict of callables in place of a long
if/elifladder.
None of these need special syntax. The language has no “callback keyword” or “strategy object” — you just pass a function. That’s the entire payoff of first-class status.