When you first learn to write Python functions, you usually define an exact number of parameters and pass in the exact same number of arguments. If a function asks for two pieces of data, you give it two pieces of data. But real-world software is rarely that rigid.
What if you are building an application that needs to calculate the average score of a student, but one student took three tests while another took fifteen? What if you want to provide a user the option to customize their profile, but want to automatically assign a default avatar if they choose to skip that step?
Standard positional arguments are too strict for these scenarios. To make your code truly dynamic and adaptable, Python provides advanced argument parsing techniques: Default Arguments, Keyword Arguments, and Arbitrary Arguments (*args and **kwargs).
Table of Contents
What is Function Arguments?
In Python programming, the data you define in a function’s parentheses are called parameters, and the actual data you pass in when calling the function are called arguments. Python supports several advanced argument types to maximize flexibility:
- Default Arguments: Parameters that are assigned a fallback value during the function’s definition. If the caller does not provide a value for that parameter, the default is automatically used.
- Keyword Arguments: Arguments passed to a function by explicitly declaring the parameter’s name, allowing you to bypass the strict left-to-right positional order.
- Arbitrary Arguments (
*args/**kwargs): A packing mechanism utilizing the asterisk operators (*and**) that allows a function to accept a variable, completely unknown number of incoming arguments.
Syntax & Basic Usage
Normally, if you define a function with two parameters, failing to provide exactly two arguments will crash your program with a TypeError. Default Arguments solve this by making specific inputs optional.
To create a default argument, you simply use the assignment operator (=) right inside the function’s definition.
# 'status' is a default argument. It is completely optional when calling the function.
def create_system_user(username, status="Active"):
print(f"Creating user '{username}'. Account status set to: {status}")
# 1. Calling the function with BOTH arguments
create_system_user("DataScientist99", "Admin")
# 2. Calling the function with ONLY the required argument
create_system_user("NewEmployee")
# Expected Output:
# Creating user 'DataScientist99'. Account status set to: Admin
# Creating user 'NewEmployee'. Account status set to: Active
Code language: PHP (php)
Python Function Arguments: *args and **kwargs
Let’s dive deep into how to manipulate, name, and gather arguments to build highly flexible Python functions.
1. Keyword Arguments (Ignoring Positional Order)
By default, Python matches arguments to parameters based on their position from left to right. This is called positional passing. However, if a function has many parameters, remembering the exact order is difficult.
Keyword Arguments allow you to explicitly name the parameter you are targeting when you call the function, meaning you can pass the data in any order you want.
def book_flight(origin, destination, seat_class):
print(f"Booking flight from {origin} to {destination}. Class: {seat_class}")
# Passing arguments strictly by position (Standard)
book_flight("New York", "London", "Economy")
# Passing arguments using Keywords (Order no longer matters!)
book_flight(seat_class="Business", destination="Tokyo", origin="San Francisco")
# Expected Output:
# Booking flight from New York to London. Class: Economy
# Booking flight from San Francisco to Tokyo. Class: Business
Code language: PHP (php)
2. Arbitrary Positional Arguments (*args)
Sometimes, you have no idea how many arguments a user might pass into your function. By placing a single asterisk * before a parameter name, you instruct Python to gather all remaining positional arguments and pack them securely into a Tuple.
(Note: While *args is the industry standard naming convention, the magic comes entirely from the * symbol. You could name it *numbers or *names if you prefer).
# The * tells Python to collect all provided arguments into a tuple named 'args'
def calculate_total_revenue(*args):
# Because 'args' is a tuple, we can use built-in functions like sum() or iterate over it
total_revenue = sum(args)
print(f"Total revenue processed from {len(args)} transactions: ${total_revenue}")
return total_revenue
# Passing a variable number of arguments
calculate_total_revenue(150.00, 50.00)
calculate_total_revenue(19.99, 5.50, 100.00, 45.00, 8.99)
# Expected Output:
# Total revenue processed from 2 transactions: $200.0
# Total revenue processed from 5 transactions: $179.48
Code language: PHP (php)
3. Arbitrary Keyword Arguments (**kwargs)
What if you want to allow users to pass in an unknown number of named keyword arguments? By using double asterisks ** before a parameter name, Python collects all unmapped keyword arguments and packs them into a Dictionary.
This is incredibly useful when building user profiles, configuring software, or interacting with web APIs where the incoming data fields are unpredictable.
# The ** tells Python to collect all keyword arguments into a dictionary named 'kwargs'
def build_employee_profile(first_name, last_name, **kwargs):
# We start with a base dictionary
profile = {
"First Name": first_name,
"Last Name": last_name
}
# We merge the base dictionary with the newly captured 'kwargs' dictionary
profile.update(kwargs)
# Printing the organized data
print(f"--- Profile for {first_name} ---")
for key, value in profile.items():
print(f"{key}: {value}")
print() # Blank line for readability
# Calling the function with varying amounts of keyword data
build_employee_profile("Alice", "Smith", department="Engineering", remote=True)
build_employee_profile("Bob", "Jones", role="Manager", office="NYC", clearance_level=5)
# Expected Output:
# --- Profile for Alice ---
# First Name: Alice
# Last Name: Smith
# department: Engineering
# remote: True
#
# --- Profile for Bob ---
# First Name: Bob
# Last Name: Jones
# role: Manager
# office: NYC
# clearance_level: 5
Code language: PHP (php)
4. The Ultimate Combo (The Order of Parameters)
You can use all of these techniques within a single function! However, Python enforces a very strict mathematical order in which you must declare your parameters. If you mix up this order, you will trigger a SyntaxError.
The Strict Parameter Order:
- Standard Positional Parameters
- Default Parameters
*args(Arbitrary Positional)**kwargs(Arbitrary Keyword)
# A master function utilizing every parameter type in the correct order
def master_logger(system_name, severity="INFO", *error_codes, **system_metadata):
print(f"[{severity}] System: {system_name}")
if error_codes:
print(f"Triggered Error Codes: {error_codes}")
if system_metadata:
print(f"Metadata Diagnostics: {system_metadata}")
# Calling the master function
master_logger("PaymentGateway", "CRITICAL", 404, 500, 502, user_id=992, retry_attempt=3)
# Expected Output:
# [CRITICAL] System: PaymentGateway
# Triggered Error Codes: (404, 500, 502)
# Metadata Diagnostics: {'user_id': 992, 'retry_attempt': 3}
Code language: PHP (php)
Real-World Practical Examples
Scenario 1: Flexible Receipt Generator
E-commerce applications often need to process a flexible number of purchased items (*args) while applying specific, optional discounts or tax rates (**kwargs).
def generate_receipt(*item_prices, **discounts):
subtotal = sum(item_prices)
final_total = subtotal
print("--- STORE RECEIPT ---")
print(f"Subtotal ({len(item_prices)} items): ${subtotal:.2f}")
# Process dynamic keyword arguments if they exist
if "promo_code" in discounts:
discount_amount = subtotal * discounts["promo_code"]
final_total -= discount_amount
print(f"Promo Applied: -${discount_amount:.2f}")
if "employee_discount" in discounts:
flat_discount = discounts["employee_discount"]
final_total -= flat_discount
print(f"Employee Discount: -${flat_discount:.2f}")
print(f"FINAL TOTAL: ${final_total:.2f}\n")
# Customer 1: Bought 3 items, used a 20% promo code
generate_receipt(15.00, 25.50, 10.00, promo_code=0.20)
# Customer 2: Bought 5 items, has a $15 employee discount
generate_receipt(5.00, 5.00, 10.00, 8.00, 12.00, employee_discount=15.00)
# Expected Output:
# --- STORE RECEIPT ---
# Subtotal (3 items): $50.50
# Promo Applied: -$10.10
# FINAL TOTAL: $40.40
#
# --- STORE RECEIPT ---
# Subtotal (5 items): $40.00
# Employee Discount: -$15.00
# FINAL TOTAL: $25.00
Code language: PHP (php)
Scenario 2: Dynamic HTML Tag Generator
When building web frameworks (like Django or Flask), functions frequently use **kwargs to transform arbitrary Python keyword arguments directly into HTML attributes.
# *content gathers any text, **attributes gathers HTML properties like 'class' or 'id'
def create_html_element(tag_name, *content, **attributes):
# 1. Format the attributes into an HTML string (e.g., class="btn" id="submit")
attr_string = ""
for attr_key, attr_value in attributes.items():
attr_string += f' {attr_key}="{attr_value}"'
# 2. Join the content together
inner_html = " ".join(content)
# 3. Construct the final HTML tag
return f"<{tag_name}{attr_string}>{inner_html}</{tag_name}>"
# Generating a button
button_html = create_html_element("button", "Click", "Here", id="submit-btn", style="color: red;")
print(button_html)
# Expected Output:
# <button id="submit-btn" style="color: red;">Click Here</button>
Code language: PHP (php)
Best Practices & Common Pitfalls
- The Mutable Default Argument Bug: This is one of the most notorious and dangerous bugs in Python. Never use a mutable object (like a List
[]or Dictionary{}) as a default argument. Because default arguments are evaluated only once when the function is defined, that exact same list will be shared continuously across every single call to the function, causing your data to bleed and duplicate.# ❌ BAD: The list is shared across all function calls! def add_to_cart(item, cart=[]): pass # ✅ GOOD: Use 'None' and create a fresh list inside the function def add_to_cart(item, cart=None): if cart is None: cart = [] cart.append(item) return cart - Naming Conventions: While the asterisks (
*and**) are the actual operators performing the magic, using the names*argsand**kwargsis a globally recognized Python convention. If you rename them to*dataand**metadata, your code will run perfectly, but other developers might find it slightly harder to read. Stick to*argsand**kwargsunless a highly specific domain name is much clearer. - Overusing Arbitrary Arguments: While
*argsand**kwargsare powerful, don’t use them as a lazy way to avoid defining clear parameters. If a function definitively requires ausernameand anemail, explicitly define those parameters! Only use arbitrary arguments when the incoming data is genuinely unknown or highly variable.
Summary
- Default Arguments (
param="value") make inputs optional by providing a fallback value. - Keyword Arguments allow you to bypass positional ordering by explicitly naming your parameters during the function call.
*argsuses a single asterisk to pack an unknown number of positional arguments into a Tuple.**kwargsuses double asterisks to pack an unknown number of keyword arguments into a Dictionary.- If you combine all parameter types in a single function, you must follow the strict order: Positional, Default,
*args,**kwargs. - Never use a mutable object (like an empty list or dictionary) as a default argument to prevent data-bleeding bugs.
