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
74: Closures Explained
You've likely encountered situations where you need a function to "remember" some piece of data, but you don't want to commit to the overhead of a full-blown class. I've seen a lot of developers struggle with this, usually falling into the trap of using global variables or passing the same configuration argument into a function ten times a day. That's where closures come in.
The Global State Trap
Imagine you're building a system to calculate sales tax for different regions. A naive approach might look like this:
TAX_RATE = 0.05
def calculate_total(price):
return price + (price * TAX_RATE)
# Now we need to calculate for a different region
TAX_RATE = 0.08
print(calculate_total(100)) # 108.0
This feels fine for a script with ten lines, but it's a nightmare in a real production environment. Why? Because your logic is now tied to a global variable. If you're running an asynchronous task or a multi-threaded server, one request might change the TAX_RATE while another request is halfway through a calculation. You've created a race condition. You can't have two different tax calculators existing at the same time without them fighting over that one global variable.
Over-Engineering with Classes
The immediate "correct" software engineering answer is usually to wrap this in a class. I'll admit, I've done this too many times when a closure would have been cleaner.
class TaxCalculator:
def __init__(self, rate):
self.rate = rate
def calculate(self, price):
return price + (price * self.rate)
ny_calc = TaxCalculator(0.08)
ca_calc = TaxCalculator(0.07)
This works perfectly. It's encapsulated and safe. But look at the boilerplate. We had to define an __init__, manage self, and instantiate an object just to use one single method. If the only thing your class does is hold one variable and provide one function, you're basically using a class as a glorified function. It's not "wrong," but it's noisy.
Capturing State with Closures
A closure allows you to associate data with a function without using a class. It happens when you define a function inside another function, and the inner function references a variable from the outer function's scope. Even after the outer function finishes executing, the inner function "closes over" that variable, keeping it alive.
def make_tax_calculator(rate):
# This is the outer function
def calculate(price):
# This inner function 'remembers' the rate from above
return price + (price * rate)
return calculate
# Create specialized functions
ny_calc = make_tax_calculator(0.08)
ca_calc = make_tax_calculator(0.07)
print(ny_calc(100)) # 108.0
print(ca_calc(100)) # 107.0
Notice what happened here. make_tax_calculator didn't just return a value; it returned a function. The ny_calc variable is now a function that has its own private copy of rate tucked away in its memory. This is a closure.
Why this actually matters
The trade-off here is between explicitness and brevity. Closures are incredibly powerful for creating "function factories" or decorators (which we'll dive into later). They give you the data encapsulation of a class but with the lightweight syntax of a function.
I generally recommend closures when you have a "one-method class" scenario. If you find yourself writing a class where the only method besides __init__ is a single operation, stop and ask if a closure is a better fit. It reduces the cognitive load for whoever reads your code next because they don't have to track object state—they just have a function that does exactly one thing with a predefined configuration.
📋 Practical Task
Build a Persistent API Request Header Generator
In many professional projects, you have to make multiple API calls to the same service using the same authentication token and content type, but with different endpoints. Instead of passing the token into every single function call, use a closure to create a "request generator."
Your Task:
- Create a function called
make_request_formatterthat accepts anapi_tokenand aversionstring (e.g., "v1"). - This function should return a nested function called
format_url. - The
format_urlfunction should take anendpointstring as an argument and return a full formatted string in this exact format:"Endpoint: /v1/users | Token: abc123xyz | Path: /profile". - Instantiate two different formatters (one for "v1" and one for "v2") and use them to print the formatted strings for the
"/settings"endpoint.
There are no comments for now.