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
331: Function Composition Patterns in Python
You’ve probably run into this a dozen times: you have a few small, focused functions that each do one thing well, and you need to run a piece of data through all of them in a specific order. The instinct for most of us is to just nest them. It feels direct. It’s "just Python." But as the chain grows, the code starts to look more like a Matryoshka doll than a professional codebase.
The Matryoshka Doll Problem
Imagine we're building a system to sanitize user-submitted usernames before they hit the database. We need to strip whitespace, lowercase everything, and then remove any characters that aren't alphanumeric. In a naive implementation, it looks like this:
def strip_space(text):
return text.strip()
def lowercase(text):
return text.lower()
def alpha_numeric(text):
return "".join(char for char in text if char.isalnum())
# The naive approach
username = " User_Name_123! "
cleaned = alpha_numeric(lowercase(strip_space(username)))
Now, for three functions, this is tolerable. But I've seen this pattern grow to six or seven levels of nesting in production code. When you're reading this, you have to read from the inside out—right to left—which is the opposite of how we read English. If I tell you to add a "length check" step between lowercasing and alphanumeric filtering, you have to carefully peel back a layer of parentheses, insert the function, and hope you didn't break the nesting. It's brittle and a headache to debug.
Creating a Formal Composition Pipeline
I prefer to treat function composition as a first-class citizen. Instead of nesting calls, we can create a compose utility. The goal is to pass a list of functions and a value, and have Python handle the "piping" for us. We can use functools.reduce to achieve this elegantly.
from functools import reduce
def compose(*funcs):
"""Returns a function that is the composition of the given functions."""
return lambda x: reduce(lambda v, f: f(v), funcs, x)
# Now we define our pipeline as a reusable object
sanitize_username = compose(
strip_space,
lowercase,
alpha_numeric
)
username = " User_Name_123! "
print(sanitize_username(username)) # "username123"
See the difference? Now the logic reads top-to-bottom. If you need to add a new step, you just add a new function to the compose arguments. I love this because it separates the what (the sequence of transformations) from the how (the act of calling them). You've essentially created a domain-specific pipeline for your data.
The Trade-off: Abstraction vs. Transparency
I should be honest with you: this isn't always the right move. If you're only combining two functions once in your entire app, compose is overkill. You're introducing a lambda and a reduce call where a simple nested call would have sufficed. You're adding a layer of abstraction that a junior dev might find confusing if they aren't familiar with functional programming patterns.
However, the moment you find yourself writing "wrapper" functions that do nothing but call other functions in a sequence, you've hit the tipping point. At that stage, the clarity of a pipeline outweighs the "magic" of the compose utility. It turns your data transformation into a configuration problem rather than a logic problem, which is where you want to be as your system scales.
📋 Practical Task
Build a Log Message Sanitization Pipeline
You are tasked with cleaning up raw system logs before they are sent to an analytics dashboard. The logs are messy and contain sensitive information that must be removed.
Requirements:
- Create three separate functions:
remove_timestamp: Removes everything before the first space (assuming the timestamp is always the first word).mask_emails: Replaces any string containing an "@" symbol with the string"[EMAIL]".truncate_log: Ensures the final string is no longer than 50 characters, appending "..." if it is.
- Implement a
composefunction (usingreduce) to combine these three steps. - Apply the resulting pipeline to the following test string:
"2023-10-12 14:00:01 Error: User admin@company.com failed to upload a very large file to the server".
Expected Output: "Error: User [EMAIL] failed to upload a very l..."
There are no comments for now.