How to handle missing or invalid function arguments in Python

PythonPythonBeginner
Practice Now

Introduction

Python's function arguments are a fundamental aspect of programming, but dealing with missing or invalid arguments can be challenging. This tutorial will guide you through handling function arguments in Python, from understanding the basics to implementing robust validation and error handling strategies. By the end, you will be able to write Python code that gracefully manages function arguments, leading to more reliable and maintainable applications.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/BasicConceptsGroup(["Basic Concepts"]) python(("Python")) -.-> python/ControlFlowGroup(["Control Flow"]) python(("Python")) -.-> python/FunctionsGroup(["Functions"]) python(("Python")) -.-> python/ErrorandExceptionHandlingGroup(["Error and Exception Handling"]) python/BasicConceptsGroup -.-> python/type_conversion("Type Conversion") python/ControlFlowGroup -.-> python/conditional_statements("Conditional Statements") python/FunctionsGroup -.-> python/function_definition("Function Definition") python/FunctionsGroup -.-> python/arguments_return("Arguments and Return Values") python/FunctionsGroup -.-> python/default_arguments("Default Arguments") python/FunctionsGroup -.-> python/keyword_arguments("Keyword Arguments") python/ErrorandExceptionHandlingGroup -.-> python/catching_exceptions("Catching Exceptions") subgraph Lab Skills python/type_conversion -.-> lab-398011{{"How to handle missing or invalid function arguments in Python"}} python/conditional_statements -.-> lab-398011{{"How to handle missing or invalid function arguments in Python"}} python/function_definition -.-> lab-398011{{"How to handle missing or invalid function arguments in Python"}} python/arguments_return -.-> lab-398011{{"How to handle missing or invalid function arguments in Python"}} python/default_arguments -.-> lab-398011{{"How to handle missing or invalid function arguments in Python"}} python/keyword_arguments -.-> lab-398011{{"How to handle missing or invalid function arguments in Python"}} python/catching_exceptions -.-> lab-398011{{"How to handle missing or invalid function arguments in Python"}} end

Understanding Basic Function Arguments and Default Values

In Python, functions are blocks of reusable code that perform specific tasks. When you define a function, you can specify parameters that the function expects to receive. Let's learn how to set up functions with different types of arguments and explore how to provide default values.

Creating Our First Function

Let's start by creating a simple Python file to work with. In the WebIDE, navigate to the project directory and create a new file called function_args.py:

  1. Click on the "File" menu in the WebIDE
  2. Select "New File"
  3. Enter function_args.py as the filename
  4. Click "OK"

Now, let's add a basic function to this file:

def greet(name):
    """A simple function that greets a person by name."""
    return f"Hello, {name}!"

## Call the function and print the result
result = greet("Alice")
print(result)

Save the file (Ctrl+S or File > Save) and then run it in the terminal:

python3 function_args.py

You should see the output:

Hello, Alice!

Understanding Required Arguments

In the example above, name is a required argument. If you try to call the function without providing this argument, Python will raise an error.

Let's modify our file to demonstrate this:

def greet(name):
    """A simple function that greets a person by name."""
    return f"Hello, {name}!"

## This will work
result = greet("Alice")
print(result)

## This will raise an error
try:
    result = greet()
    print(result)
except TypeError as e:
    print(f"Error: {e}")

Save the file and run it:

python3 function_args.py

Output:

Hello, Alice!
Error: greet() missing 1 required positional argument: 'name'

As you can see, Python raises a TypeError when we don't provide the required argument.

Adding Default Values

To make arguments optional, we can provide default values. Let's update our function:

def greet(name="Guest"):
    """A function that greets a person by name, with a default value."""
    return f"Hello, {name}!"

## With an argument
result1 = greet("Alice")
print(result1)

## Without an argument - uses the default value
result2 = greet()
print(result2)

Save and run:

python3 function_args.py

Output:

Hello, Alice!
Hello, Guest!

Now the function works both with and without an argument.

Multiple Arguments with Defaults

Let's expand our function to handle multiple arguments, some with default values:

def greet(name="Guest", message="Hello", punctuation="!"):
    """A function with multiple arguments and default values."""
    return f"{message}, {name}{punctuation}"

## Using all default values
print(greet())

## Providing only the name
print(greet("Alice"))

## Providing name and message
print(greet("Bob", "Hi"))

## Providing all arguments
print(greet("Charlie", "Welcome", "!!!"))

Save and run:

python3 function_args.py

Output:

Hello, Guest!
Hello, Alice!
Hi, Bob!
Welcome, Charlie!!!

Using Keyword Arguments

You can also specify arguments by name, regardless of their order:

def greet(name="Guest", message="Hello", punctuation="!"):
    """A function with multiple arguments and default values."""
    return f"{message}, {name}{punctuation}"

## Using keyword arguments
print(greet(message="Hey", name="David"))
print(greet(punctuation="...", message="Welcome back", name="Emma"))

Save and run:

python3 function_args.py

Output:

Hey, David!
Welcome back, Emma...

This is particularly useful when a function has many arguments, and you only want to specify a few of them.

Now you understand how to create functions with default arguments and how to use keyword arguments. In the next step, we'll explore more advanced ways to handle missing or invalid function arguments.

Handling Missing Arguments with *args and **kwargs

In Python, we sometimes need to create flexible functions that can accept a variable number of arguments. To handle these cases, Python provides two special syntax elements: *args and **kwargs.

Creating a New Python File

Let's create a new file to work with these concepts:

  1. Click on the "File" menu in the WebIDE
  2. Select "New File"
  3. Enter flexible_args.py as the filename
  4. Click "OK"

Understanding *args

The *args syntax allows a function to accept any number of positional arguments, which are collected into a tuple.

Add the following code to flexible_args.py:

def sum_numbers(*args):
    """A function that sums up any number of arguments."""
    result = 0
    for num in args:
        result += num
    return result

## Test the function with different numbers of arguments
print(f"Sum of 1, 2: {sum_numbers(1, 2)}")
print(f"Sum of 1, 2, 3, 4, 5: {sum_numbers(1, 2, 3, 4, 5)}")
print(f"No arguments: {sum_numbers()}")

Save the file and run it:

python3 flexible_args.py

Output:

Sum of 1, 2: 3
Sum of 1, 2, 3, 4, 5: 15
No arguments: 0

This demonstrates how *args can handle any number of arguments, including none at all. Inside the function, args is a tuple containing all the arguments provided.

Understanding **kwargs

The **kwargs syntax allows a function to accept any number of keyword arguments, which are collected into a dictionary.

Let's add another function to our file:

def build_profile(**kwargs):
    """A function that builds a user profile from keyword arguments."""
    profile = {}

    ## Add required fields with defaults
    profile["name"] = kwargs.get("name", "Anonymous")
    profile["age"] = kwargs.get("age", "Not specified")

    ## Add any additional fields
    for key, value in kwargs.items():
        if key not in ["name", "age"]:
            profile[key] = value

    return profile

## Test the function with different keyword arguments
print("Basic profile:", build_profile())
print("Full profile:", build_profile(name="Alice", age=30, occupation="Developer", location="New York"))
print("Custom fields:", build_profile(hobby="Reading", favorite_color="Blue"))

Save and run:

python3 flexible_args.py

Output:

Basic profile: {'name': 'Anonymous', 'age': 'Not specified'}
Full profile: {'name': 'Alice', 'age': 30, 'occupation': 'Developer', 'location': 'New York'}
Custom fields: {'name': 'Anonymous', 'age': 'Not specified', 'hobby': 'Reading', 'favorite_color': 'Blue'}

Notice how kwargs.get("key", default_value) allows us to retrieve values with defaults if they don't exist.

Combining Required, Default, *args, and **kwargs

Let's create a more complex function that combines all these types of arguments:

def format_message(recipient, message="Hello", *args, **kwargs):
    """
    A function that formats a message with various customization options.
    - recipient: Required - who the message is for
    - message: Default greeting
    - *args: Additional message parts
    - **kwargs: Formatting options
    """
    ## Start with the basic message
    full_message = f"{message}, {recipient}!"

    ## Add any additional message parts
    if args:
        full_message += " " + " ".join(args)

    ## Apply formatting options
    if kwargs.get("upper", False):
        full_message = full_message.upper()

    if kwargs.get("wrap", False):
        full_message = f"[{full_message}]"

    return full_message

## Test with different combinations
print(format_message("Alice"))
print(format_message("Bob", "Hi"))
print(format_message("Charlie", "Welcome", "Hope", "you", "are", "well"))
print(format_message("David", "Greetings", upper=True))
print(format_message("Emma", wrap=True))
print(format_message("Frank", "Hey", "How's it going?", upper=True, wrap=True))

Save and run:

python3 flexible_args.py

Output:

Hello, Alice!
Hi, Bob!
Welcome, Charlie! Hope you are well
GREETINGS, DAVID!
[Hello, Emma!]
[HEY, FRANK! HOW'S IT GOING?]

This example demonstrates how to use all types of function arguments together:

  1. recipient is a required positional argument
  2. message has a default value, making it optional
  3. *args captures any additional positional arguments
  4. **kwargs captures any keyword arguments

By combining these approaches, you can create highly flexible functions that handle missing or optional arguments gracefully.

Validating Function Arguments

When creating functions in Python, it's important to verify that the arguments passed to your functions are valid before proceeding with the function's main logic. In this step, we'll learn several techniques for validating function arguments.

Creating a New Python File

Let's create a new file to work with validation concepts:

  1. Click on the "File" menu in the WebIDE
  2. Select "New File"
  3. Enter validate_args.py as the filename
  4. Click "OK"

Basic Validation with Conditionals

The simplest way to validate arguments is to use conditional statements. Let's start with some basic validations:

def calculate_rectangle_area(length, width):
    """Calculate the area of a rectangle, validating inputs."""
    ## Validate that inputs are numbers
    if not isinstance(length, (int, float)):
        raise TypeError("Length must be a number")
    if not isinstance(width, (int, float)):
        raise TypeError("Width must be a number")

    ## Validate that inputs are positive
    if length <= 0:
        raise ValueError("Length must be positive")
    if width <= 0:
        raise ValueError("Width must be positive")

    ## Calculate the area
    return length * width

## Test with valid inputs
try:
    area = calculate_rectangle_area(5, 3)
    print(f"Area of rectangle: {area}")
except (TypeError, ValueError) as e:
    print(f"Error: {e}")

## Test with invalid types
try:
    area = calculate_rectangle_area("5", 3)
    print(f"Area of rectangle: {area}")
except (TypeError, ValueError) as e:
    print(f"Error: {e}")

## Test with invalid values
try:
    area = calculate_rectangle_area(5, -3)
    print(f"Area of rectangle: {area}")
except (TypeError, ValueError) as e:
    print(f"Error: {e}")

Save and run:

python3 validate_args.py

Output:

Area of rectangle: 15
Error: Length must be a number
Error: Width must be positive

This function validates both the types and values of its arguments before performing any calculations. When invalid arguments are detected, appropriate error messages are raised.

Using Assertions for Validation

Another way to validate arguments is to use assertions. Assertions are statements that raise an AssertionError if a condition is not met:

def calculate_discount(price, discount_percent):
    """Calculate the discounted price."""
    ## Assert that inputs are valid
    assert isinstance(price, (int, float)), "Price must be a number"
    assert isinstance(discount_percent, (int, float)), "Discount must be a number"
    assert price >= 0, "Price cannot be negative"
    assert 0 <= discount_percent <= 100, "Discount must be between 0 and 100"

    ## Calculate the discount
    discount_amount = price * (discount_percent / 100)
    return price - discount_amount

## Test with valid inputs
try:
    discounted_price = calculate_discount(100, 20)
    print(f"Discounted price: ${discounted_price}")
except AssertionError as e:
    print(f"Error: {e}")

## Test with invalid discount percentage
try:
    discounted_price = calculate_discount(100, 120)
    print(f"Discounted price: ${discounted_price}")
except AssertionError as e:
    print(f"Error: {e}")

Save and run:

python3 validate_args.py

Output:

Discounted price: $80.0
Error: Discount must be between 0 and 100

Assertions are useful for development and debugging, but they can be disabled in production code, so they're not always the best choice for validation in real-world applications.

Using Type Hints for Documentation

Python 3.5+ supports type hints, which can help document the expected types of function arguments and return values. While type hints don't perform runtime validation by themselves, they provide useful documentation and can be checked by external tools like mypy:

def calculate_average(numbers: list[float]) -> float:
    """Calculate the average of a list of numbers."""
    if not numbers:
        raise ValueError("Cannot calculate average of empty list")

    if not all(isinstance(n, (int, float)) for n in numbers):
        raise TypeError("All elements must be numbers")

    return sum(numbers) / len(numbers)

## Test with valid input
try:
    avg = calculate_average([1, 2, 3, 4, 5])
    print(f"Average: {avg}")
except (TypeError, ValueError) as e:
    print(f"Error: {e}")

## Test with empty list
try:
    avg = calculate_average([])
    print(f"Average: {avg}")
except (TypeError, ValueError) as e:
    print(f"Error: {e}")

## Test with non-numeric elements
try:
    avg = calculate_average([1, 2, "3", 4, 5])
    print(f"Average: {avg}")
except (TypeError, ValueError) as e:
    print(f"Error: {e}")

Save and run:

python3 validate_args.py

Output:

Average: 3.0
Error: Cannot calculate average of empty list
Error: All elements must be numbers

Note that the type hints (list[float] and -> float) don't perform any validation by themselves - we still need to write our own validation code. They serve as documentation and can be checked by external tools.

Creating a Robust Function with Validation

Now, let's apply all these techniques to build a robust function that calculates the total cost of items with a discount:

def calculate_total_cost(items=None, tax_rate=0, discount=0):
    """
    Calculate the total cost of items with tax and discount.

    Args:
        items: List of (item_name, price) tuples
        tax_rate: Tax rate percentage (0-100)
        discount: Discount percentage (0-100)

    Returns:
        A dictionary with the total, subtotal, tax amount, and discount amount
    """
    ## Validate items
    if items is None:
        items = []

    if not isinstance(items, list):
        raise TypeError("Items must be a list")

    ## Validate each item in the list
    for i, item in enumerate(items):
        if not isinstance(item, tuple) or len(item) != 2:
            raise ValueError(f"Item {i} must be a tuple of (name, price)")

        name, price = item
        if not isinstance(name, str):
            raise TypeError(f"Name of item {i} must be a string")
        if not isinstance(price, (int, float)):
            raise TypeError(f"Price of item {i} must be a number")
        if price < 0:
            raise ValueError(f"Price of item {i} cannot be negative")

    ## Validate tax_rate and discount
    if not isinstance(tax_rate, (int, float)):
        raise TypeError("Tax rate must be a number")
    if not isinstance(discount, (int, float)):
        raise TypeError("Discount must be a number")

    if not (0 <= tax_rate <= 100):
        raise ValueError("Tax rate must be between 0 and 100")
    if not (0 <= discount <= 100):
        raise ValueError("Discount must be between 0 and 100")

    ## Calculate the total
    subtotal = sum(price for _, price in items)
    discount_amount = subtotal * (discount / 100)
    tax_amount = (subtotal - discount_amount) * (tax_rate / 100)
    total = subtotal - discount_amount + tax_amount

    return {
        "subtotal": subtotal,
        "discount_amount": discount_amount,
        "tax_amount": tax_amount,
        "total": total
    }

## Test with valid inputs
shopping_cart = [
    ("Laptop", 1000),
    ("Mouse", 25),
    ("Keyboard", 45)
]

try:
    result = calculate_total_cost(shopping_cart, tax_rate=8.5, discount=10)
    print("Shopping Cart Total:")
    for key, value in result.items():
        print(f"  {key.replace('_', ' ').title()}: ${value:.2f}")
except (TypeError, ValueError) as e:
    print(f"Error: {e}")

## Test with invalid item
try:
    invalid_cart = [
        ("Laptop", 1000),
        ("Mouse", "twenty-five"),  ## Invalid price
        ("Keyboard", 45)
    ]
    result = calculate_total_cost(invalid_cart)
    print(result)
except (TypeError, ValueError) as e:
    print(f"Error with invalid item: {e}")

Save and run:

python3 validate_args.py

Output:

Shopping Cart Total:
  Subtotal: $1070.00
  Discount Amount: $107.00
  Tax Amount: $81.86
  Total: $1044.86
Error with invalid item: Price of item 1 must be a number

This function demonstrates robust validation by:

  1. Checking the types of all inputs
  2. Validating the range of numeric values
  3. Providing detailed error messages
  4. Setting reasonable defaults for optional parameters
  5. Using docstrings to document the expected inputs and return values

By implementing thorough validation in your functions, you can prevent errors, provide better feedback to users, and make your code more robust and maintainable.

Building a Complete Application

Now that we have learned various techniques for handling and validating function arguments, let's apply these skills to build a simple but complete application. We will create a basic expense tracking system that demonstrates good practices for function argument handling.

Creating the Application File

Let's create a new Python file for our expense tracker:

  1. Click on the "File" menu in the WebIDE
  2. Select "New File"
  3. Enter expense_tracker.py as the filename
  4. Click "OK"

Designing the Expense Tracker Functions

Our expense tracker will have several functions that handle different aspects of expense management:

def create_expense(description, amount, category=None, date=None):
    """
    Create a new expense entry.

    Args:
        description (str): Description of the expense
        amount (float): The amount spent
        category (str, optional): Category of the expense
        date (str, optional): The date in YYYY-MM-DD format

    Returns:
        dict: An expense entry
    """
    ## Validate description
    if not isinstance(description, str):
        raise TypeError("Description must be a string")
    if not description:
        raise ValueError("Description cannot be empty")

    ## Validate amount
    if not isinstance(amount, (int, float)):
        raise TypeError("Amount must be a number")
    if amount <= 0:
        raise ValueError("Amount must be positive")

    ## Create the expense dictionary
    expense = {
        "description": description,
        "amount": float(amount),
        "category": category or "Uncategorized",
        "date": date or "Not specified"
    }

    return expense


def add_expense_to_list(expenses, **kwargs):
    """
    Add a new expense to the expenses list.

    Args:
        expenses (list): The list of expenses
        **kwargs: The expense details to be passed to create_expense

    Returns:
        list: The updated list of expenses
    """
    ## Validate the expenses list
    if not isinstance(expenses, list):
        raise TypeError("Expenses must be a list")

    ## Extract required arguments
    if "description" not in kwargs:
        raise ValueError("Expense description is required")
    if "amount" not in kwargs:
        raise ValueError("Expense amount is required")

    ## Create the expense and add it to the list
    expense = create_expense(
        kwargs["description"],
        kwargs["amount"],
        kwargs.get("category"),
        kwargs.get("date")
    )

    expenses.append(expense)
    return expenses


def get_total_expenses(expenses, category=None):
    """
    Calculate the total amount of expenses, optionally filtered by category.

    Args:
        expenses (list): The list of expenses
        category (str, optional): Filter by this category if provided

    Returns:
        float: The total amount
    """
    ## Validate the expenses list
    if not isinstance(expenses, list):
        raise TypeError("Expenses must be a list")

    ## Calculate the total
    if category:
        return sum(e["amount"] for e in expenses if e["category"] == category)
    else:
        return sum(e["amount"] for e in expenses)


def get_expense_summary(expenses):
    """
    Get a summary of expenses by category.

    Args:
        expenses (list): The list of expenses

    Returns:
        dict: A dictionary with categories as keys and total amounts as values
    """
    ## Validate the expenses list
    if not isinstance(expenses, list):
        raise TypeError("Expenses must be a list")

    ## Create the summary
    summary = {}
    for expense in expenses:
        category = expense["category"]
        if category in summary:
            summary[category] += expense["amount"]
        else:
            summary[category] = expense["amount"]

    return summary

Using Our Expense Tracker

Now let's use our functions to track some expenses:

def print_expense_summary(summary):
    """Print a formatted summary of expenses by category."""
    print("\nExpense Summary by Category:")
    print("-" * 30)
    for category, amount in summary.items():
        print(f"{category}: ${amount:.2f}")
    print("-" * 30)
    print(f"Total: ${sum(summary.values()):.2f}")

## Initialize an empty expenses list
expenses = []

## Add some expenses
try:
    ## Add with required arguments only
    expenses = add_expense_to_list(
        expenses,
        description="Groceries",
        amount=45.75
    )

    ## Add with all arguments
    expenses = add_expense_to_list(
        expenses,
        description="Movie tickets",
        amount=25.00,
        category="Entertainment",
        date="2023-11-15"
    )

    ## Add another expense
    expenses = add_expense_to_list(
        expenses,
        description="Dinner",
        amount=65.40,
        category="Food",
        date="2023-11-14"
    )

    ## Add with default category
    expenses = add_expense_to_list(
        expenses,
        description="Gas",
        amount=35.80,
        date="2023-11-16"
    )

    ## Display all expenses
    print("All Expenses:")
    for i, expense in enumerate(expenses, 1):
        print(f"{i}. {expense['description']}: ${expense['amount']:.2f} " +
              f"({expense['category']}, {expense['date']})")

    ## Get and display the total
    total = get_total_expenses(expenses)
    print(f"\nTotal expenses: ${total:.2f}")

    ## Get and display expenses for a specific category
    food_total = get_total_expenses(expenses, "Food")
    print(f"Food expenses: ${food_total:.2f}")

    ## Get and display the summary
    summary = get_expense_summary(expenses)
    print_expense_summary(summary)

except (TypeError, ValueError) as e:
    print(f"Error: {e}")

Let's also add some code to demonstrate error handling:

## Try some invalid inputs
print("\nTesting error handling:")

try:
    ## Invalid expense description
    expenses = add_expense_to_list(expenses, description="", amount=10)
except ValueError as e:
    print(f"Caught error: {e}")

try:
    ## Invalid expense amount
    expenses = add_expense_to_list(expenses, description="Coffee", amount=-5)
except ValueError as e:
    print(f"Caught error: {e}")

try:
    ## Missing required argument
    expenses = add_expense_to_list(expenses, description="Coffee")
except ValueError as e:
    print(f"Caught error: {e}")

Save the file and run:

python3 expense_tracker.py

Expected output:

All Expenses:
1. Groceries: $45.75 (Uncategorized, Not specified)
2. Movie tickets: $25.00 (Entertainment, 2023-11-15)
3. Dinner: $65.40 (Food, 2023-11-14)
4. Gas: $35.80 (Uncategorized, 2023-11-16)

Total expenses: $171.95
Food expenses: $65.40

Expense Summary by Category:
------------------------------
Uncategorized: $81.55
Entertainment: $25.00
Food: $65.40
------------------------------
Total: $171.95

Testing error handling:
Caught error: Description cannot be empty
Caught error: Amount must be positive
Caught error: Expense amount is required

Application Review

Our expense tracker demonstrates several important concepts:

  1. Argument validation: Each function validates its arguments to ensure they meet the expected types and constraints.

  2. Default values: We use default values to make certain arguments optional, like category and date.

  3. Required arguments: For essential information like description and amount, we ensure these are provided and valid.

  4. Keyword arguments: The add_expense_to_list function uses **kwargs to accept expense details in a flexible way.

  5. Error handling: We use appropriate exceptions with meaningful error messages to make debugging easier.

  6. Docstrings: Each function includes a docstring that explains its purpose, arguments, and return values.

By applying these techniques, we've created a robust application that handles function arguments in a reliable and user-friendly way. This approach helps prevent bugs, improves code maintainability, and provides clear feedback when something goes wrong.

Summary

In this tutorial, you've learned essential techniques for handling missing or invalid function arguments in Python:

  1. Understanding basic function arguments and defaults: You've explored different types of function arguments, including required arguments, optional arguments with default values, and keyword arguments. This foundation helps you design functions that are both flexible and user-friendly.

  2. Handling variable numbers of arguments: You've learned how to use *args and **kwargs to create functions that can accept any number of arguments, making your functions more adaptable to different use cases.

  3. Validating function arguments: You've implemented various validation techniques to ensure that arguments meet expected types and constraints, helping to prevent bugs and provide clear error messages.

  4. Building a complete application: You've applied these concepts to build a practical expense tracking application, demonstrating how proper argument handling leads to robust and maintainable code.

These skills are fundamental to writing reliable Python code. By properly handling function arguments, you can create functions that are more flexible, easier to use, and less prone to errors. As you continue your Python journey, these techniques will help you build more sophisticated applications that can handle unexpected inputs and edge cases gracefully.