Previous | Tutorial index | Next
Functions become far more powerful when you understand how to pass arguments to them. Python offers a flexible and expressive argument‑handling system that allows you to write functions that can be called in many different ways. In this tutorial, we will explore positional arguments (the default), keyword arguments (by name), default parameter values, and variable‑length arguments (*args and **kwargs). We’ll also cover the correct order of parameters, the concept of positional‑only and keyword‑only arguments, and how arguments are passed (the “pass by assignment” model). By the end, you’ll be able to design functions with clear, intuitive calling conventions.
Positional arguments are the simplest form: the arguments are matched to parameters in the order they are passed.
def subtract(a, b):
return a - b
print(subtract(10, 3)) # 7 (a=10, b=3)
print(subtract(3, 10)) # -7 (a=3, b=10)
The order matters.
If a parameter has no default value, it is required. Calling the function without enough arguments raises a TypeError.
def greet(name):
print(f"Hello, {name}!")
greet() # TypeError: missing 1 required positional argument: 'name'
You can specify which argument goes to which parameter by using the parameter name. This makes the call more explicit and allows you to change the order.
def introduce(name, age, city):
print(f"{name} is {age} years old and lives in {city}.")
introduce(city="Paris", age=25, name="Alice") # Works fine
You can mix them, but positional arguments must come before keyword arguments in the call.
introduce("Bob", city="London", age=30) # valid: "Bob" -> name, then keyword for age, city
# introduce(city="London", "Bob", age=30) # SyntaxError: positional argument follows keyword argument
You can assign a default value to a parameter. If the caller does not provide an argument for that parameter, the default is used.
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
print(greet("Alice")) # Hello, Alice!
print(greet("Bob", "Hi")) # Hi, Bob!
Parameters with defaults are optional. They must come after any required parameters (without defaults) in the definition.
def set_timeout(seconds, message="Timeout!"): # valid: required first, default second
...
def set_timeout(message="Timeout!", seconds): # SyntaxError: non-default argument follows default argument
Default values are evaluated once when the function is defined, not each time it is called. This can cause surprising behavior when the default is a mutable object (like a list or dictionary).
Example of the trap:
def append_to_list(value, my_list=[]):
my_list.append(value)
return my_list
print(append_to_list(1)) # [1]
print(append_to_list(2)) # [1, 2] <-- unexpected!
The same list object is used across calls. To fix, use None as a sentinel.
def append_to_list(value, my_list=None):
if my_list is None:
my_list = []
my_list.append(value)
return my_list
Rule of thumb: Never use mutable objects as default values unless you explicitly want to share state (which is rare).
Python uses a model called “pass by assignment” (also known as “pass by object reference” or “call by sharing”).
def modify_list(lst):
lst.append(4) # modifies the original list
lst = [1, 2, 3] # rebinds local variable; does not affect original
nums = [10, 20, 30]
modify_list(nums)
print(nums) # [10, 20, 30, 4] (append worked; reassignment did not)
This is similar to how Java works with objects. Immutable objects (like integers, strings, tuples) cannot be changed in place, so you always get new objects.
*args and **kwargsSometimes you want a function to accept an arbitrary number of arguments. This is where *args and **kwargs come in.
*args – Variable‑Length Positional ArgumentsWhen you prefix a parameter with *, it collects all extra positional arguments into a tuple (named args by convention, but you can use any name).
def sum_all(*args):
total = 0
for num in args:
total += num
return total
print(sum_all(1, 2, 3)) # 6
print(sum_all(10, 20, 30, 40)) # 100
print(sum_all()) # 0 (empty tuple)
You can combine *args with normal parameters, but *args must come after all positional parameters.
def multiply(multiplier, *numbers):
return [multiplier * n for n in numbers]
print(multiply(2, 1, 2, 3)) # [2, 4, 6]
**kwargs – Variable‑Length Keyword ArgumentsSimilarly, **kwargs collects extra keyword arguments into a dictionary.
def print_profile(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
print_profile(name="Alice", age=30, city="NYC")
# name: Alice
# age: 30
# city: NYC
*args and **kwargsIt’s common to see def func(*args, **kwargs): to accept any combination. This is useful for decorators or wrapper functions.
def logger(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with args={args}, kwargs={kwargs}")
return func(*args, **kwargs)
return wrapper
There is a strict order for parameters in a function definition:
* – a special marker to separate positional‑only from keyword‑only (if you want to force keyword‑only after it).**kwargs – must come last.In practice, the typical order without positional‑only is:
def func(positional_required, positional_optional=default, *args, keyword_only, **kwargs):
pass
If you want to force certain parameters to be passed only by keyword, place them after a *.
def greet(name, *, greeting="Hello", punctuation="!"):
return f"{greeting}, {name}{punctuation}"
# Both are valid:
greet("Alice") # Hello, Alice! (uses defaults)
greet("Bob", greeting="Hi") # Hi, Bob!
# greet("Bob", "Hi") # TypeError: greet() takes 1 positional argument but 2 were given
The * itself does not collect arguments; it marks the end of positional parameters.
You can make parameters positional‑only by placing a / before them. Parameters before / cannot be passed by keyword.
def divmod(a, b, /):
return a // b, a % b
divmod(10, 3) # (3, 1)
# divmod(a=10, b=3) # TypeError: divmod() got some positional-only arguments passed as keyword arguments
This is used in some built‑in functions (like len()) and is mostly for library design.
You can also use * and ** when calling a function to unpack a sequence or dictionary into arguments.
* – unpack a list/tuple into positional arguments.** – unpack a dict into keyword arguments.def add(a, b, c):
return a + b + c
numbers = [1, 2, 3]
print(add(*numbers)) # 6
info = {"a": 5, "b": 10, "c": 15}
print(add(**info)) # 30
This is very handy when you already have the data in a collection.
def show_info(name, age, *hobbies, city="Unknown", **extras):
print(f"Name: {name}, Age: {age}")
if hobbies:
print(f"Hobbies: {', '.join(hobbies)}")
print(f"City: {city}")
if extras:
print("Extra info:")
for key, value in extras.items():
print(f" {key}: {value}")
show_info("Alice", 30, "reading", "swimming", city="Paris", job="Engineer", pet="cat")
Output:
Name: Alice, Age: 30
Hobbies: reading, swimming
City: Paris
Extra info:
job: Engineer
pet: cat
What is a positional argument?
Given def f(a, b=2, c=3): return a+b+c, what does f(1, c=10) return?
61314ErrorWhat is the purpose of *args in a function definition?
True or False: Mutable default values (like []) are safe and recommended because they allow caching.
What is the correct order of parameters in a function definition?
*args, positional, default, **kwargs*args, keyword-only, **kwargs**kwargs, *args, positional*argsWhat does this code print?
def func(x, y, *args):
return x + y + sum(args)
print(func(1, 2, 3, 4))
1067TypeErrorHow can you force a parameter to be passed only by keyword?
* before that parameter./ before that parameter.**kwargs.What is the difference between *args in definition and *list in a call?
*args collects arguments; *list unpacks a list into arguments.*args is for dictionaries; *list is for lists.*list syntax.What does def my_func(**kwargs): allow you to do?
Given def test(a, b, /, c, *, d):, which call is invalid?
test(1, 2, 3, d=4)test(1, b=2, c=3, d=4)test(a=1, b=2, c=3, d=4)test(1, 2, c=3, d=4)Exercise 1: Default Argument
Write a function greet_user(name, title="Mr.") that returns a string like "Mr. John" or "Ms. Jane".
print(greet_user("John")) # Mr. John print(greet_user("Jane", "Ms.")) # Ms. Jane
</details>
**Exercise 2: Sum with Variable Arguments**
Write a function `product(*numbers)` that returns the product of all numbers; if none, return `1`.
<details><summary>Sample Solution</summary>
```python
def product(*numbers):
result = 1
for n in numbers:
result *= n
return result
print(product(2, 3, 4)) # 24
print(product()) # 1
Exercise 3: Keyword‑Only Arguments
Write a function create_student(name, age, *, grade, school) that requires grade and school as keyword arguments. Return a dictionary.
student = create_student("Bob", 20, grade="A", school="MIT") print(student)
</details>
**Exercise 4: Unpacking**
Write a function `introduce_person(name, age, city)` and call it using a tuple unpacked with `*` and a dictionary unpacked with `**`.
<details><summary>Sample Solution</summary>
```python
def introduce_person(name, age, city):
print(f"{name} is {age} years old and lives in {city}.")
# using tuple
person = ("Alice", 25, "London")
introduce_person(*person)
# using dict
info = {"name": "Bob", "age": 30, "city": "Paris"}
introduce_person(**info)
Exercise 5: Flexible Logging
Write a function log(level, message, **extras) that prints the level and message, then each extra key‑value pair.
log("INFO", "User logged in", user_id=101, session="abc123")
</details>
---
### 🏠 Homework – Deeper Thinking
#### Short Answer Questions
**1. Arg Parser Helper**
Write a function `make_url(base_url, *paths, **params)` that builds a URL.
<details><summary>Sample Answer</summary>
```python
def make_url(base_url, *paths, **params):
if paths:
base_url = base_url.rstrip('/') + '/' + '/'.join(paths)
if params:
base_url += '?' + '&'.join(f"{k}={v}" for k, v in params.items())
return base_url
print(make_url("https://api.com", "v1", "users", id=5, sort="asc"))
# https://api.com/v1/users?id=5&sort=asc
2. Function with Positional‑Only and Keyword‑Only
Write calculate(a, b, /, operation, *, round_result=False) that performs the operation and optionally rounds.
3. Decorator with *args and **kwargs
Write a decorator timer that measures and prints execution time.
4. Configuration Builder
Write a function create_config(**settings) that returns a dictionary with default settings overridden by keyword arguments.
print(create_config(debug=True)) print(create_config(log_level='ERROR', max_retries=5))
</details>
**5. Arbitrary Argument Forwarding**
Write a function `wrapper(func, *args, **kwargs)` that prints the function name and arguments, then calls and returns the result.
<details><summary>Sample Answer</summary>
```python
def wrapper(func, *args, **kwargs):
print(f"Calling {func.__name__} with args={args}, kwargs={kwargs}")
return func(*args, **kwargs)
def add(a, b, c=0):
return a + b + c
print(wrapper(add, 1, 2, c=3)) # prints and returns 6
"/".join(paths). If paths, join with base + '/' + path. For params, use '&'.join(f"{k}={v}" for k,v in params.items()). Combine with ?.if/elif for operations; handle division by zero. Round if needed.import time; record start, call function, print difference.defaults = {'debug': False, ...}; then settings = defaults.copy(); settings.update(kwargs); return settings.func(*args, **kwargs).In this tutorial, you have learned:
*args and **kwargs for flexible function signatures.Mastering these concepts allows you to write functions that are both flexible and clear, with intuitive calling interfaces that work well in a variety of scenarios.
Next Steps: In Tutorial 5, we will explore Lambda Functions and Functional Programming Tools like map, filter, and reduce.
Happy argument handling!