In Python, functions are first-class citizens. This means a function is treated like any other object — it can be:
- Assigned to a variable
- Passed as an argument to another function
- Stored in a data structure
- Returned from another function
Assignment to Variables
Assigning a function reference to a variable without invoking it (no parentheses ()).
def greet(name):
return f'Hello, {name}!'
# Assign reference, not the result
say_hello = greet
print(say_hello('Alice')) # Output: Hello, Alice!
Both names point to the same function object in memory:
print(greet is say_hello) # Output: True
print(id(greet) == id(say_hello)) # Output: True
This works because greet without () evaluates to the function object itself, not its return value.
Passing Functions as Arguments
Passing a function reference to another function to customize behavior.
TIP
A function that accepts another function as an argument is called a higher-order function.
def apply_operation(func, val):
return func(val)
def double(n):
return n * 2
print(apply_operation(double, 5)) # Output: 10
This is the same pattern Python’s built-in map and filter use:
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x ** 2, numbers))
print(squared) # Output: [1, 4, 9, 16, 25]
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # Output: [2, 4]
map receives a function and applies it to every element. filter receives a function and keeps only elements where it returns True. Neither would work if functions couldn’t be passed as arguments.
Storing in Collections
Storing function references inside lists, dictionaries, or tuples to execute them dynamically.
def increment(x): return x + 1
def decrement(x): return x - 1
ops = {
'inc': increment,
'dec': decrement
}
print(ops['inc'](10)) # Output: 11
Dispatch Table Pattern
This is commonly used to replace long if/elif chains with a clean lookup:
def add(a, b): return a + b
def sub(a, b): return a - b
def mul(a, b): return a * b
dispatch = {
'+': add,
'-': sub,
'*': mul
}
# Look up and call in one step
op = '+'
result = dispatch[op](10, 3)
print(result) # Output: 13
Instead of branching through conditions, the dictionary maps a key directly to the function that handles it.
Returning Functions from Functions
An outer function can define a nested inner function and return its reference without executing it.
TIP
A function that returns another function is also a higher-order function.
def outer_func(x):
def inner_func(y):
return x + y
return inner_func
# Returns reference to inner_func
add_five = outer_func(5)
# Executes inner_func(10)
print(add_five(10)) # Output: 15
Step-by-Step Execution
- Step 1:
outer_func(5)is invoked. It creates a local scope wherex = 5and definesinner_func. - Step 2:
outer_funcreturns the reference toinner_funcwithout calling it, and its execution frame ends. - Step 3: The variable
add_fivestores the returnedinner_funcreference. - Step 4: When
add_five(10)is invoked, it runsinner_func(10)wherey = 10. It accessesx = 5from the parent environment and returns15.
Reference Flow Visualization
flowchart TD
subgraph Global [Global Scope]
G1(1. Call outer_func 5)
G2(4. Store inner_func reference as add_five)
G3(5. Call add_five 10)
G4(8. Receive return value 15)
end
subgraph Local [outer_func Scope]
L1(2. Set x = 5)
L2(3. Return inner_func reference)
end
subgraph Inner [inner_func Scope]
I1(6. Set y = 10)
I2(7. Fetch x = 5)
end
G1 --> L1
L1 --> L2
L2 --> G2
G2 --> G3
G3 --> I1
I1 --> I2
I2 -->|Read x| L1
I2 --> G4
Notice that inner_func still accesses x after outer_func has finished executing. This behavior — where a nested function remembers its enclosing scope — is called a closure.