18.2 Positional and Keyword Arguments
In Python, function arguments are a powerful and flexible mechanism for passing data into a function. They can be broadly categorized into two types: positional arguments and keyword arguments. Understanding the distinction and interaction between these two is fundamental to writing clear, robust, and maintainable functions.
The Nature of Positional Arguments
Positional arguments are the most basic form of argument passing. Their values are mapped to the function’s parameters based solely on their order, or position, in the function call. The first argument value is assigned to the first parameter, the second value to the second parameter, and so on. This behavior is intuitive and mirrors how arguments are passed in many other programming languages.
def describe_pet(animal_type, pet_name):
"""Display information about a pet."""
print(f"\nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
# The order of arguments matters.
describe_pet('hamster', 'harry') # Correct: hamster -> animal_type, harry -> pet_name
describe_pet('harry', 'hamster') # Incorrect logic: harry -> animal_type, hamster -> pet_name
In the incorrect call above, the function will execute without a syntax error, but the output will be logically wrong (“I have a harry. My harry’s name is Hamster.”). This highlights a key pitfall of relying solely on position: it can lead to subtle bugs if the caller accidentally reverses the order of arguments.
The Clarity of Keyword Arguments
Keyword arguments, often called named arguments, free you from the constraint of order. You explicitly specify which parameter each argument is meant for by using the name of the parameter (keyword=value) in the function call. This greatly enhances code readability and reduces the risk of errors caused by passing arguments in the wrong order.
# Using keyword arguments, order becomes irrelevant.
describe_pet(pet_name='harry', animal_type='hamster')
describe_pet(animal_type='hamster', pet_name='harry')
Both calls above are functionally identical and will produce the correct output. The function call becomes self-documenting; anyone reading the code can immediately see which value corresponds to which parameter. This is a critical best practice, especially for functions that take several parameters or have parameters that are not intuitively ordered.
Combining Positional and Keyword Arguments
Python allows you to mix positional and keyword arguments in a single function call. However, a crucial rule governs this mixture: all positional arguments must come before any keyword arguments. This rule exists because the interpreter first assigns values positionally. If a keyword argument were allowed first, the interpreter would have no way of knowing how to map subsequent positional arguments to the remaining parameters.
def greet_user(greeting, first_name, last_name):
"""Print a personalized greeting."""
print(f"{greeting}, {first_name} {last_name}!")
# Valid: positional, then keyword
greet_user('Hello', first_name='Jane', last_name='Doe')
greet_user('Hello', 'Jane', last_name='Doe')
# Invalid: keyword before positional (will raise a SyntaxError)
# greet_user(first_name='Jane', 'Hello', last_name='Doe')
The last line, if uncommented, would cause a SyntaxError because the keyword argument first_name='Jane' precedes the positional argument 'Hello'.
Default Values and Their Interaction with Argument Types
Default parameter values add another layer of flexibility. When a parameter has a default value, it becomes optional during the function call. This is a common technique for creating functions that have sensible defaults.
def format_name(first_name, last_name, middle_name='', use_formal=False):
"""Return a formatted full name."""
full_name = f"{first_name}"
if middle_name:
full_name += f" {middle_name}"
full_name += f" {last_name}"
if use_formal:
return f"Mr./Ms. {full_name}"
return full_name
# All these calls are valid due to default values.
print(format_name('John', 'Doe'))
print(format_name('John', 'Doe', use_formal=True))
print(format_name('Mary', 'Smith', 'Alice'))
print(format_name('Mary', 'Smith', middle_name='Alice', use_formal=True))
Parameters with default values must be defined after parameters without defaults in the function’s definition. This rule ensures that the required (non-default) parameters can always be satisfied by positional arguments. From a calling perspective, once you use a keyword argument in a call, all subsequent arguments must also be keywords. This prevents ambiguity. For example, in the call format_name('John', 'Doe', True), it’s impossible to tell if True is meant for middle_name or use_formal. Forcing the use of keywords (use_formal=True) after the positional arguments eliminates this ambiguity.