Python Function Parameters: Types, Usage, and Best Practices
Two Categories of Function Parameters
Formal Parameters
Formal parameters (形参) are the variable names defined in the function's parentheses during the definition phase.
def calculate(a, b):
pass
# a and b are formal parameters
Actual Arguments
Actual arguments (实参) are the values passed into the function's parentheses during the call phase.
def calculate(a, b):
return a + b
calculate(5, 10)
# 5 and 10 are actual arguments
Relationship Between Formal and Actual Arguments
The binding between formal and actual parameters occurs at call time and breaks when the function exits. Formal parameters only receive variable names, while actual arguments can be values, variable names, or function return values—essentially anything that evaluates to a value.
Positional Parameters
Positional parameters are defined in order from left to right in the function's parameter list.
Positional Formal Parameters
Variables defined in order during function definition are positional formal parameters.
def compare(x, y, z):
pass
# x, y, z are positional formal parameters
Positional Actual Arguments
Values passed in order during function call are positional actual arguments.
compare(100, 50, 200)
# 100, 50, 200 are positional actual arguments
Key characteristic: Positional parameters require a one-to-one correspondence between formal and actual arguments.
def find_maximum(first, second):
return first if first > second else second
# Correct usage
result = find_maximum(30, 15)
# Incorrect - wrong number of arguments
# find_maximum(10, 20, 30) # Too many arguments
# find_maximum(10) # Too few arguments
Keyword Arguments
Keyword arguments allow passing values by explicitly naming parameters, bypassing positional order.
result = find_maximum(second=25, first=100)
Important rule: Positional arguments must appear before keyword arguments. The following is invalid:
# Invalid syntax
# result = find_maximum(second=10, 20)
# Valid syntax
result = find_maximum(20, second=10)
Default Parameters
Default parameters have predefined values assigned during function definition.
def greet(name, greeting="Hello"):
return f"{greeting}, {name}"
print(greet("Alice")) # Uses default: "Hello, Alice"
print(greet("Bob", "Hi")) # Uses provided: "Hi, Bob"
Default Parameter Pitfall with Mutable Objects
When a default parameter is a mutable object (like a list or dictionary), all calls share the same object:
def add_skill(name, skills=[]):
skills.append(name)
print(skills)
add_skill("Python") # Output: ['Python']
add_skill("JavaScript") # Output: ['Python', 'JavaScript']
add_skill("Go") # Output: ['Python', 'JavaScript', 'Go']
The correct approach uses None as a sentinel value:
def add_skill(name, skills=None):
if skills is None:
skills = []
skills.append(name)
print(skills)
Default Values Are Fixed at Definition Time
Default values are evaluated once when the function is defined, not at call time:
limit = 100
def validate(value, threshold=limit):
print(value, threshold)
limit = 200
validate(50) # Output: 50 100
Variable-Length Arguments
Collecting Excess Positional Arguments with *args
The * operator captures any extra positional arguments into a tuple:
def sum_all(x, y, *remaining):
total = x + y
for num in remaining:
total += num
return total
result = sum_all(1, 2, 3, 4, 5, 6)
# x=1, y=2, remaining=(3, 4, 5, 6), returns 21
Collecting Excess Keyword Arguments with **kwargs
The ** operator captures extra keyword arguments into a dictionary:
def display_config(name, **options):
print(f"Name: {name}")
print(f"Options: {options}")
display_config("Server", port=8080, debug=True, timeout=30)
# Name: Server
# Options: {'port': 8080, 'debug': True, 'timeout': 30}
By convention, *args and **kwargs are the standard names, where args stands for arguments and kwargs for keyword arguments.
Using * and ** in Actual Arguments
When calling functions, ** unpacks dictionaries into keyword arguments:
def create_user(username, email, age, role):
print(f"User: {username}, Email: {email}, Age: {age}, Role: {role}")
config = {"username": "admin", "email": "admin@example.com",
"age": 30, "role": "superuser"}
create_user(**config)
Similarly, * can unpack iterables into positional arguments.
Combining All Parameter Types
A function can accept various parameter types in this order:
def process(a, b, c=10, *args, **kwargs):
print(f"Required: a={a}, b={b}")
print(f"Default: c={c}")
print(f"Extra positional: {args}")
print(f"Extra keyword: {kwargs}")
process(1, 2, 3, 4, 5, mode="fast", debug=True)
# Required: a=1, b=2
# Default: c=3
# Extra positional: (4, 5)
# Extra keyword: {'mode': 'fast', 'debug': True}
Keyword-Only Parameters
Parameters appearing after a bare * must be passed as keyword arguments only:
def send_notification(to, subject, *, urgent=False, high_priority=False):
print(f"To: {to}, Subject: {subject}")
print(f"Urgent: {urgent}, High Priority: {high_priority}")
# Valid calls
send_notification("user@example.com", "Meeting reminder")
send_notification("user@example.com", "Alert!", urgent=True)
# Invalid - positional passing for keyword-only params
# send_notification("user@example.com", "Alert!", True) # TypeError
This syntax forces explicit parameter names, making code more readable and preventing accidental positional argument mistakes, particularly useful in large codebases and APIs.