Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
186: Functions as First-Class Objects Revisited
We've touched on first-class functions before, but usually in the context of simple map() or filter() calls. In a real production codebase, treating functions as first-class objects isn't just a neat trick; it's how we build flexible, decoupled systems. I want to show you how this looks when you're building a system that needs to change its behavior dynamically without a giant if/elif/else block.
Let's build a simple Notification Dispatcher. Imagine we have different ways to alert a user—email, SMS, or a push notification—and we want to decide which one to use based on the severity of the event.
Defining our handler logic
First, I'll write a few standalone functions. Each one does one thing and takes the same set of arguments. This consistency is key; if the signatures don't match, passing them around becomes a nightmare of *args and **kwargs.
def send_email(message):
print(f"Sending Email: {message}")
def send_sms(message):
print(f"Sending SMS: {message}")
def send_slack(message):
print(f"Posting to Slack: {message}")
Creating a function registry
Instead of writing a long conditional block every time I want to send a notification, I'm going to store these functions in a dictionary. Because functions are first-class objects, I can use the function name (without parentheses) as a value in the dictionary. I'm essentially creating a "lookup table" for behavior.
# Mapping severity levels to specific functions
NOTIFICATION_STRATEGY = {
"low": send_slack,
"medium": send_email,
"high": send_sms
}
The mistake: Executing too early
Now, here is where I usually trip up when I'm rushing. I'll try to write the dispatcher function, but I might accidentally call the function while trying to assign it or retrieve it. Look at this mistake I just made:
def notify(severity, message):
# BUG: I'm calling the function immediately here by adding ()
# This will try to call send_slack() and assign its RETURN value to handler
handler = NOTIFICATION_STRATEGY.get(severity, send_slack)()
handler(message)
If you ran this, it would crash. Why? Because NOTIFICATION_STRATEGY.get(severity, send_slack)() executes the function right then and there. Since send_slack returns None, the variable handler becomes None, and then I try to call None(message). Classic.
I need to retrieve the function object itself, and then call it on the next line.
Executing the retrieved function
Here is the corrected version. I grab the function reference first, then I invoke it. I'm also using .get() to provide a sensible default (Slack) in case a severity level is passed that I haven't defined.
def notify(severity, message):
# Grab the function reference
handler = NOTIFICATION_STRATEGY.get(severity, send_slack)
# Now invoke the reference
handler(message)
# Testing it out
notify("high", "The server is on fire!") # Output: Sending SMS: The server is on fire!
notify("low", "Coffee is ready.") # Output: Posting to Slack: Coffee is ready.
notify("unknown", "Something happened.") # Output: Posting to Slack: Something happened.
By treating the functions as objects, I've made the notify function completely agnostic about how the notification is sent. If we need to add a "PagerDuty" notification tomorrow, I don't have to touch the notify logic at all; I just add a new function and a new entry to the NOTIFICATION_STRATEGY dictionary.
📋 Practical Task
Build a Plugin-based Data Validator
You are building a data ingestion tool. You need to create a system that validates a string based on a "validation type" passed in as a key.
- Create three functions:
is_numeric(returns True if the string is all digits),is_email(returns True if '@' is in the string), andis_long(returns True if the string is longer than 10 characters). - Create a dictionary called
VALIDATOR_REGISTRYthat maps the strings"number","email", and"length"to their respective functions. - Write a function called
validate_data(data_type, value). This function should look up the validator in your registry and return the result of that validator when called withvalue. - If a
data_typeis provided that isn't in the registry, the function should returnFalse.
There are no comments for now.