Functions
First-Class Functions
Functions as objects that can be assigned, passed, and stored
Interview: Core Python concept — underpins decorators, callbacks, and functional programming
In Python, functions are first-class objects (citizens). This means they can be assigned to variables, stored in data structures, passed as arguments to other functions, and returned from functions — just like any other object (int, string, list). This is a foundational concept that enables decorators, callbacks, higher-order functions, and the entire functional programming paradigm in Python.
What First-Class Means
- Assign to variables:
my_func = some_function— creates a reference, not a copy - Pass as arguments:
sorted(items, key=len)— len is passed as a function object - Return from functions:
def make_adder(n): return lambda x: x + n - Store in data structures: Lists, dicts, sets can all contain function objects
Function Object Attributes
- __name__: The function's name as a string
- __doc__: The docstring (or None if not documented)
- __module__: The module where the function was defined
- __qualname__: Fully qualified name (includes class name for methods)
- __annotations__: Type hints as a dictionary
- __defaults__: Tuple of default parameter values
- __closure__: Tuple of cells containing captured variables (for closures)
Custom Function Attributes
Since functions are objects, you can set custom attributes on them: func.count = 0. This is useful for stateful functions without needing a class, but prefer closures or classes for complex state management.
Dispatch Tables
One of the most practical uses of first-class functions is building dispatch tables — dictionaries that map keys to functions. This replaces long if/elif chains with a clean, extensible lookup:
- Dict of functions:
{op: function}— look up and call the right function - Command pattern: Map command names to handler functions
- Strategy pattern: Swap algorithms at runtime by passing different functions
Use Cases
Building dispatch tables and command patterns to replace long if/elif chains
Implementing callback-based APIs and event-driven architectures
Dependency injection — passing functions to make code testable and flexible
Storing handler functions in registries for plugin systems
Strategy pattern — swapping algorithms at runtime without code changes
Common Mistakes
Calling the function instead of passing it — func() executes, func passes the object
Using custom function attributes instead of closures/classes for complex state
Not realizing that assigning a function doesn't copy it — both names reference the same object
Forgetting that lambda functions have __name__ = "<lambda>" — hard to debug in dispatch tables
Overusing dispatch tables when a simple if/elif is clearer for 2-3 conditions