Function Parameters & Arguments
Parameters are the names a function declares; arguments are the values you hand it — and Python gives you several precise ways to connect the two.
A function's *parameters* are the variables listed in its def line; the *arguments* are the actual values supplied at the call site. Python's calling convention is unusually flexible: arguments can be matched by position, by keyword name, or forced into one mode or the other, and parameters can carry defaults or soak up arbitrary extras. Understanding the rules precisely — not just by example — is what lets you read any function signature and know exactly how it can legally be called.
Positional Arguments
Positional arguments are matched to parameters purely by their left-to-right order in the call. This is the default and most familiar way to call a function.
Get the order wrong and Python won't complain — it will silently bind the wrong value to the wrong name, which is why keyword arguments exist.
def describe(name, age, city):
return f"{name} is {age} and lives in {city}"
print(describe("Riya", 29, "Pune"))
# name='Riya', age=29, city='Pune' — matched purely by positionKeyword Arguments
Keyword arguments are matched by explicitly naming the parameter at the call site: name=value. Order no longer matters, and the call becomes self-documenting.
Once you use a keyword argument in a call, every argument after it must also be a keyword argument — you cannot follow a keyword argument with a bare positional one.
print(describe(age=29, city="Pune", name="Riya"))
# same result — keyword arguments can appear in any order
# Mixing is allowed, but positional arguments must come first
print(describe("Riya", city="Pune", age=29))Positional-Only Parameters (/)
A / in the parameter list (added in Python 3.8) marks every parameter before it as *positional-only*: callers cannot supply those arguments by keyword. This is useful for parameter names that are implementation details, or to keep a signature free to rename those parameters later without breaking callers.
Many built-ins like len() and pow() are positional-only under the hood — that's why len(obj=[1,2,3]) fails.
def power(base, exp, /):
return base ** exp
power(2, 10) # OK — 1024
power(base=2, exp=10) # TypeError: 'base' is positional-onlyKeyword-Only Parameters (*)
A bare * in the parameter list marks every parameter after it as *keyword-only*: callers must supply those arguments by name. This forces clarity for flags or options that would be unreadable as bare positional values.
*args (see Variable-Length Arguments) also introduces keyword-only territory: anything declared after *args is automatically keyword-only.
def connect(host, port, *, timeout=30, retries=3):
return f"{host}:{port} timeout={timeout} retries={retries}"
connect("db.local", 5432, timeout=5) # OK
connect("db.local", 5432, 5) # TypeError: too many positional argumentsCombining / and * — the Full Signature Grammar
A single signature can mix positional-only, normal (either-way), and keyword-only sections, always in this order: positional-only, then /, then normal, then * (or *args), then keyword-only.
def request(url, /, method="GET", *, headers=None, timeout=10):
# ^positional-only ^either-way ^keyword-only
headers = headers or {}
return f"{method} {url} headers={headers} timeout={timeout}"
request("https://api.example.com")
request("https://api.example.com", "POST", headers={"X": "1"})Default Parameters (Brief)
Any parameter can carry a default value with name=value, making the argument optional. Defaults get their own dedicated topic (Default Arguments) — including the mutable-default pitfall — so this page just notes where they sit in the ordering rule below.
Parameter Ordering Rule
In a def line, parameters must appear in this fixed order: positional-only parameters, then normal parameters, then parameters with defaults, then *args (or a bare *), then keyword-only parameters, then **kwargs. Violating this order is a SyntaxError caught before the function even runs.
A non-default parameter cannot follow a default parameter in the positional-or-keyword section: def g(a=1, b): ... is a SyntaxError.
def f(a, b, /, c, d=10, *args, e, f=20, **kwargs):
pass
# a, b: positional-only
# c: positional-or-keyword, no default
# d: positional-or-keyword, has default
# args: extra positional arguments
# e: keyword-only, required
# f: keyword-only, has default
# kwargs: extra keyword argumentsArgument Unpacking at the Call Site
The * and ** operators also work in reverse, at a *call*, to unpack a sequence into positional arguments or a mapping into keyword arguments.
def describe(name, age, city):
return f"{name}, {age}, {city}"
args = ("Riya", 29, "Pune")
print(describe(*args)) # unpack tuple -> positional args
kwargs = {"name": "Riya", "age": 29, "city": "Pune"}
print(describe(**kwargs)) # unpack dict -> keyword args
print(describe(*["Riya"], **{"age": 29, "city": "Pune"})) # can combinePractical Usage: Designing Readable Signatures
Use / to lock down parameter names you might rename later (library-facing APIs, wrapper functions). Use * to force clarity on options that are easy to confuse if passed positionally (booleans, counts, similarly-typed values). Leave everyday parameters as flexible either-way parameters unless you have a specific reason to restrict them.
# Confusing without keyword-only enforcement:
def resize(image, width, height, keep_aspect):
...
resize(img, 800, 600, True) # what does True mean here?
# Clearer:
def resize(image, width, height, *, keep_aspect=True):
...
resize(img, 800, 600, keep_aspect=True)Edge Cases
Calling with too few or too many positional arguments, an unexpected keyword, or a duplicate binding for the same parameter (once positionally and once by keyword) all raise TypeError at call time, not at definition time.
def f(a, b):
return a + b
f(1, 2, 3) # TypeError: takes 2 positional arguments but 3 were given
f(1, a=5) # TypeError: got multiple values for argument 'a'
f(x=1, y=2) # TypeError: got an unexpected keyword argument 'x'Mental Model
Think of a call as Python building a dictionary of parameter_name -> value from left to right: positional arguments fill in slots by order first, then keyword arguments fill in remaining slots by name, then defaults fill in whatever is still empty. If any parameter ends up with no value and no default, the call fails immediately.
Quick Reference
def f(pos_only, /, either, *, kw_only):
^--- before / : positional-only
^--- between / and * : either way
^--- after * : keyword-only
Call-site unpacking:
f(*seq) -> seq's items become positional args
f(**mapping)-> mapping's items become keyword argsKey Takeaways
Arguments bind to parameters by position, by keyword, or a mix — but keyword arguments can never precede positional ones in a call.
/ marks parameters positional-only; * marks parameters keyword-only; both are optional signature features.
Signature order is fixed: positional-only, then either-way, then *args/*, then keyword-only, then **kwargs.
* and ** at a call site unpack a sequence or mapping into arguments — the mirror image of *args/**kwargs in a definition.
Mismatched arity, duplicate bindings, and unknown keywords all raise TypeError at call time.
def greet(name, greeting="Hello", name2): ...
def greet(name, name2, greeting="Hello"): ...
A parameter without a default cannot follow one that has a default — Python raises a SyntaxError at definition time, before the function is ever called.
describe("Riya", name="Riya", age=29, city="Pune") describe("Riya", age=29, city="Pune")
Passing the same parameter both positionally and by keyword gives it two values at once, which Python rejects with 'got multiple values for argument'.
def connect(host, port, timeout=30, retries=3): ... connect("h", 5432, 5, 1) # what do 5 and 1 mean here?
def connect(host, port, *, timeout=30, retries=3): ... connect("h", 5432, timeout=5, retries=1)
Leaving option-like parameters positional invites unreadable calls; a bare * forces callers to name them, which also prevents accidental argument-order mistakes.
What is the difference between a parameter and an argument?
A parameter is the name declared in a function's def line; an argument is the actual value supplied at a specific call. 'name' in def f(name) is a parameter; 'Riya' in f('Riya') is an argument.
Why must keyword arguments come after positional arguments in a call?
Because positional matching is resolved left-to-right by position; once a keyword argument appears, Python needs an unambiguous way to know which remaining slots are filled, so it disallows bare positional arguments afterward.
What does a bare / in a parameter list do, and why would you use it?
It marks every parameter before it as positional-only, meaning callers cannot pass those arguments by keyword. It's used to hide parameter names as implementation detail so they can be renamed later without breaking callers.
Given def f(a, b, *, c, d=1): ..., which arguments are required and how must c be passed?
a and b are required and can be passed positionally or by keyword; c is required and keyword-only (must be passed as c=value); d is optional and keyword-only.
What happens if you call a function with an argument count that doesn't match its required parameters?
Python raises a TypeError at call time (e.g. 'missing 1 required positional argument' or 'takes 2 positional arguments but 3 were given') — this is not caught until the call actually executes.
How would you call f(a, b, c) if you have the values stored as a list [1, 2, 3]?
Use argument unpacking: f(*[1, 2, 3]), which unpacks the list's three elements into the three positional parameters.
Why does len(obj=[1,2,3]) raise a TypeError even though len takes one parameter?
len's single parameter is positional-only (a C-implemented built-in convention), so it cannot be supplied by keyword at all — only len([1,2,3]) works.
Write a function full_name with a positional-only first name, a keyword-only middle initial (optional, default None), and a positional-or-keyword last name, that returns a formatted string.
def full_name(first, /, last, *, middle=None): if middle: return f"{first} {middle}. {last}" return f"{first} {last}" print(full_name("Ada", "Lovelace")) print(full_name("Ada", last="Lovelace", middle="K"))
'Ada Lovelace' then 'Ada K. Lovelace' — first is locked positional-only, middle must be named, last can go either way.
What does this print?
16 then 13 — in the first call b defaults to 5, giving 1+5+10=16; in the second call b=2 is passed explicitly, giving 1+2+10=13.
Write a function that accepts a dictionary of user data and calls another function create_user(name, email, *, admin=False) by unpacking that dictionary as keyword arguments.
def create_user(name, email, *, admin=False): role = "admin" if admin else "user" return f"{name} <{email}> ({role})" data = {"name": "Sam", "email": "[email protected]", "admin": True} print(create_user(**data))
'Sam <[email protected]> (admin)' — ** unpacks the dict's keys as matching keyword arguments.
What does this print, and why?
It raises a TypeError: f() got some positional-only arguments passed as keyword arguments: 'x' — x is locked positional-only by the /, so it cannot be passed as x=1.
def f(a, b=5, *, c):
return a + b + c
print(f(1, c=10))
print(f(1, 2, c=10))def f(x, /, y):
return x, y
print(f(x=1, y=2))