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
181: The functools Module: partial
You've probably encountered situations where you have a general-purpose function that takes several arguments, but in a specific part of your app, you find yourself passing the same two or three arguments over and over again. It's tedious, and it clutters your code.
That's exactly where functools.partial comes in. Instead of writing a wrapper function every time you want to "preset" some arguments, partial lets you create a new version of a function with some of the arguments already filled in. Think of it as a factory that produces specialized versions of a generic tool.
Setting up a generic price calculator
Let's build a simple price calculator for an e-commerce store. We need a function that handles the base price, a tax rate, and a flat shipping fee. In a real app, these would come from a database, but for now, let's keep it hard-coded.
def calculate_total(price, tax_rate, shipping_fee):
return (price * (1 + tax_rate)) + shipping_fee
# Without partial, it's a bit wordy:
print(calculate_total(100, 0.07, 5.00)) # Total for a $100 item
print(calculate_total(250, 0.07, 5.00)) # Total for a $250 item
If I'm building a module specifically for customers in one state (let's say, a "Standard Shipping" region with a 7% tax), I don't want to keep typing 0.07 and 5.00. It's a maintenance nightmare—if the shipping fee changes to $6.00, I have to hunt down every single call site.
Reducing the noise with partial
I can use partial to "freeze" those tax and shipping arguments. This creates a new callable that only requires the price.
from functools import partial
# I'm creating a specialized version of calculate_total
calculate_standard_total = partial(calculate_total, tax_rate=0.07, shipping_fee=5.00)
# Now I only need to pass the price
print(calculate_standard_total(100)) # 112.0
print(calculate_standard_total(250)) # 272.5
Notice how calculate_standard_total behaves exactly like a function, but it remembers those keyword arguments I passed in during its creation.
Fixing a positional argument blunder
Here is where I usually trip up when I'm moving fast. I tried to create a "Free Shipping" version of the function by passing the shipping fee as a positional argument. I thought I could just skip the tax rate and jump to the shipping fee.
# MISTAKE: I wanted to keep the tax at 0.07 but make shipping 0.00
# I tried this:
calculate_free_shipping = partial(calculate_total, 0.00)
# I expected this to be the shipping fee, but look what happens:
print(calculate_free_shipping(100, 0.07))
If you run that, you'll see it fails or gives a weird result. Why? Because partial fills in positional arguments from left to right. By passing 0.00, I didn't freeze the shipping fee; I accidentally froze the price as 0.00! Now the function thinks the price is 0, and when I call it with (100, 0.07), Python thinks 100 is the tax_rate and 0.07 is the shipping_fee.
The fix is simple: when you want to freeze arguments that aren't the first ones in the list, always use keyword arguments.
# CORRECTED:
calculate_free_shipping = partial(calculate_total, shipping_fee=0.00)
# Now the price and tax_rate are still flexible, but shipping is locked at 0.00
print(calculate_free_shipping(100, 0.07)) # 107.0
Why bother instead of using a lambda?
You might be thinking, "Why not just use lambda price: calculate_total(price, 0.07, 5.00)?" You can, and for tiny scripts, it's fine. But partial objects are more explicit and slightly more performant. More importantly, they are introspection-friendly; you can actually access the frozen arguments via the .args and .keywords attributes of the partial object, which is a lifesaver when debugging complex pipelines.
📋 Practical Task
Exercise: Creating Specialized API Request Helpers
Imagine you are writing a wrapper for an API. You have a generic function make_request(endpoint, method, timeout, api_key). To avoid passing the api_key and timeout every time, use functools.partial to create two specialized helpers:
get_user_data: A version ofmake_requestlocked to the"GET"method, a timeout of30, and the API key"SECRET_123". It should only require theendpointas an argument.post_user_data: A version locked to the"POST"method, a timeout of60, and the API key"SECRET_123". It should also only require theendpoint.
Requirements:
- Define the base
make_requestfunction that prints a string describing the request (e.g.,"Requesting /profile via GET (timeout: 30, key: SECRET_123)"). - Implement the two partial functions.
- Call both helpers once to verify they output the correct details for their respective methods and timeouts.
There are no comments for now.