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
67: Default Argument Values
In the real world, you'll find that most of the time, your functions are called with the same few arguments. If you're building a system where a value is the same 90% of the time, forcing the caller to provide that value every single time is just noise. That's where default arguments come in.
Handling the Standard Tax Rate
Let's imagine we're writing a small module for an e-commerce store. We need a function that calculates the final price of an item after adding sales tax. In our specific region, the tax is almost always 7%, but we need the flexibility to change it for international customers.
def calculate_total(price, tax_rate=0.07):
return price + (price * tax_rate)
# Most of our calls look like this:
print(calculate_total(100)) # Result: 107.0
# But for a special region, we can override it:
print(calculate_total(100, 0.15)) # Result: 115.0
By setting tax_rate=0.07 in the signature, I've made that argument optional. If the caller ignores it, Python steps in and plugs in the default. It keeps the call site clean while maintaining the power to be specific when necessary.
The Trap I Almost Fell Into
Now, here is where things get tricky. I want to add a feature that allows the function to keep a log of all the items processed in a single session. My first instinct—and a mistake I've made more times than I'd like to admit—was to just add a list as a default argument.
def process_order(item_name, price, order_log=[]):
total = price * 1.07
order_log.append(item_name)
return total, order_log
print(process_order("Laptop", 1000)) # ('1070.0', ['Laptop'])
print(process_order("Mouse", 25)) # ('26.75', ['Laptop', 'Mouse'])
Wait, look at that second output. The "Mouse" order contains the "Laptop" from the previous call. Why? Because in Python, default arguments are evaluated once at the time the function is defined, not every time the function is called. That list [] is created once and persists in memory for the entire life of the program. It's effectively a hidden global variable.
Cleaning Up the State
To fix this, we use a pattern you'll see in almost every professional Python codebase: we set the default to None and then initialize the mutable object inside the function body.
def process_order(item_name, price, order_log=None):
if order_log is None:
order_log = []
total = price * 1.07
order_log.append(item_name)
return total, order_log
# Now it behaves as expected:
print(process_order("Laptop", 1000)) # ('1070.0', ['Laptop'])
print(process_order("Mouse", 25)) # ('26.75', ['Mouse'])
By doing this, we ensure a brand new list is created every time the function is called without an explicit log, which is exactly the behavior we actually wanted.
📋 Practical Task
Building a Configurable API Request Formatter
You are building a helper function that formats a URL for an API. The API usually connects to a "production" environment, but developers need to be able to switch it to "staging" or "development" during testing.
Write a function called format_api_url that takes the following arguments:
endpoint: (Required) The specific API path (e.g., "/users" or "/orders").environment: (Default) Should default to"production".version: (Default) Should default to"v1".
The function should return a string in the format: "https://[environment].api.com/[version][endpoint]".
Test your code with these three scenarios:
- Call it with only the endpoint
"/profile". It should use both defaults. - Call it with the endpoint
"/profile"and the environment"staging". - Call it with all three arguments:
"/profile","development", and"v2".
There are no comments for now.