Skip to Content
Course content

67: Default Argument Values

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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:

  1. Call it with only the endpoint "/profile". It should use both defaults.
  2. Call it with the endpoint "/profile" and the environment "staging".
  3. Call it with all three arguments: "/profile", "development", and "v2".
Rating
0 0

There are no comments for now.

to be the first to leave a comment.