How to Add Functions to a Python Dictionary
In Python, functions are first-class objects, which means they can be assigned to variables, passed as arguments, and stored as values in dictionaries. This powerful pattern lets you map keys to callable operations, enabling clean implementations of command dispatchers, strategy patterns, calculators, and routing systems.
This guide covers different ways to add functions to dictionaries, practical applications, and best practices.
Why Store Functions in Dictionaries?
Storing functions in dictionaries replaces long chains of if/elif/else statements with a clean, extensible lookup mechanism:
# Instead of this:
def handle_command(command):
if command == 'open':
return open_file()
elif command == 'save':
return save_file()
elif command == 'close':
return close_file()
# You can do this:
commands = {'open': open_file, 'save': save_file, 'close': close_file}
result = commands[command]()
This approach is more readable, maintainable, and extensible: adding a new command means adding one dictionary entry instead of another elif branch.
Adding a Named Function
The most straightforward approach is defining a function and then assigning it as a dictionary value:
def greet(name):
return f"Hello, {name}!"
def farewell(name):
return f"Goodbye, {name}!"
messages = {
'greet': greet,
'farewell': farewell
}
# Call the function through the dictionary
print(messages['greet']('Alice'))
print(messages['farewell']('Bob'))
Output:
Hello, Alice!
Goodbye, Bob!
Notice that the function is stored without parentheses, i.e. greet, not greet(). With parentheses, you'd store the function's return value instead of the function itself:
# Correct: stores the function object
messages = {'greet': greet}
# Wrong: stores the return value of greet(): would be error here since name parameter is missing
messages = {'greet': greet()}
Adding Lambda Functions
Lambda functions are ideal for short, simple operations that don't warrant a full function definition:
math_ops = {
'square': lambda x: x ** 2,
'cube': lambda x: x ** 3,
'double': lambda x: x * 2,
'negate': lambda x: -x
}
number = 5
print(f"Square of {number}:", math_ops['square'](number))
print(f"Cube of {number}:", math_ops['cube'](number))
print(f"Double of {number}:", math_ops['double'](number))
Output:
Square of 5: 25
Cube of 5: 125
Double of 5: 10
- Use lambdas for simple, one-expression operations (e.g.,
lambda x: x * 2). - Use named functions when the logic is complex, requires multiple lines, or benefits from a descriptive name and docstring.
Adding Built-in Functions
Python's built-in functions can be stored in dictionaries just like user-defined ones:
aggregations = {
'max': max,
'min': min,
'sum': sum,
'len': len,
'sorted': sorted
}
data = [3, 1, 4, 1, 5, 9, 2, 6]
print("Max:", aggregations['max'](data))
print("Min:", aggregations['min'](data))
print("Sum:", aggregations['sum'](data))
print("Count:", aggregations['len'](data))
print("Sorted:", aggregations['sorted'](data))
Output:
Max: 9
Min: 1
Sum: 31
Count: 8
Sorted: [1, 1, 2, 3, 4, 5, 6, 9]
Adding Class Methods
You can store bound methods from class instances in a dictionary:
class StringFormatter:
def uppercase(self, text):
return text.upper()
def lowercase(self, text):
return text.lower()
def title_case(self, text):
return text.title()
formatter = StringFormatter()
format_ops = {
'upper': formatter.uppercase,
'lower': formatter.lowercase,
'title': formatter.title_case
}
text = "hello world"
print(format_ops['upper'](text))
print(format_ops['title'](text))
Output:
HELLO WORLD
Hello World
Practical Example: Simple Calculator
A calculator is a classic example of the function-dictionary pattern:
def add(x, y):
return x + y
def subtract(x, y):
return x - y
def multiply(x, y):
return x * y
def divide(x, y):
if y == 0:
return "Error: Division by zero"
return x / y
calculator = {
'+': add,
'-': subtract,
'*': multiply,
'/': divide
}
def calculate(x, operator, y):
if operator in calculator:
return calculator[operator](x, y)
return f"Unknown operator: {operator}"
print(f"10 + 5 = {calculate(10, '+', 5)}")
print(f"10 - 5 = {calculate(10, '-', 5)}")
print(f"10 * 5 = {calculate(10, '*', 5)}")
print(f"10 / 3 = {calculate(10, '/', 3)}")
print(f"10 / 0 = {calculate(10, '/', 0)}")
Output:
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50
10 / 3 = 3.3333333333333335
10 / 0 = Error: Division by zero
Practical Example: Command Dispatcher
The command pattern maps user actions to functions, commonly used in CLI tools, chatbots, and game engines:
def cmd_help():
return "Available commands: help, status, quit"
def cmd_status():
return "System status: All systems operational"
def cmd_quit():
return "Shutting down..."
commands = {
'help': cmd_help,
'status': cmd_status,
'quit': cmd_quit
}
def execute_command(command):
action = commands.get(command)
if action:
return action()
return f"Unknown command: '{command}'. Type 'help' for available commands."
print(execute_command('help'))
print(execute_command('status'))
print(execute_command('restart'))
Output:
Available commands: help, status, quit
System status: All systems operational
Unknown command: 'restart'. Type 'help' for available commands.
.get() for safe key lookupAlways use dict.get(key) instead of dict[key] when the key might not exist. This returns None instead of raising a KeyError:
# Safe: returns None if key doesn't exist
action = commands.get('unknown')
# Unsafe: raises KeyError if key doesn't exist
action = commands['unknown'] # KeyError!
Dynamically Building a Function Dictionary
You can build function dictionaries dynamically using decorators, i.e. a pattern commonly used in web frameworks:
# Registry pattern
command_registry = {}
def register(name):
"""Decorator to register a function in the command dictionary."""
def decorator(func):
command_registry[name] = func
return func
return decorator
@register('greet')
def greet_user():
return "Hello!"
@register('bye')
def say_goodbye():
return "Goodbye!"
@register('time')
def current_time():
from datetime import datetime
return f"Current time: {datetime.now().strftime('%H:%M:%S')}"
# All decorated functions are automatically registered
for name, func in command_registry.items():
print(f"{name}: {func()}")
Output:
greet: Hello!
bye: Goodbye!
time: Current time: 15:36:35
Best Practices
1. Don't call the function when adding it:
# Wrong: stores the return value, not the function
ops = {'greet': greet()}
# Correct: stores the function itself
ops = {'greet': greet}
2. Handle missing keys gracefully:
# Wrong: crashes if key doesn't exist
result = ops['nonexistent']()
# Correct: check first or use .get()
func = ops.get('nonexistent')
if func:
result = func()
else:
result = "Function not found"
Additional Guidelines
- Use descriptive key names that clearly indicate what the function does.
- Document the expected signature (parameters and return type) of functions stored in the dictionary.
- Keep the function signatures consistent across dictionary entries when possible. Functions mapped by the same dictionary should ideally accept the same parameters.
- Consider using
defaultdictor.get()with a default function for unknown keys.
Conclusion
Storing functions in Python dictionaries is a powerful pattern that replaces verbose conditional logic with clean, extensible lookup-based dispatch.
- Whether you're building a calculator, command handler, strategy selector, or route dispatcher, function dictionaries keep your code organized and easy to extend.
- Use named functions for complex logic, lambdas for simple operations, and decorators for automatic registration.
- Always handle missing keys gracefully with
.get()to build robust applications.