Python’s argument rules are unusually flexible. Learning them is what lets you read other people’s code — and the standard library — without guessing.
Positional and keyword
Arguments are matched by position by default, or by name if you name them.
def book_room(guest, nights, breakfast):
return f"{guest}: {nights} nights, breakfast={breakfast}"
print(book_room("Ada", 3, True)) # by position
print(book_room(guest="Ada", nights=3, breakfast=True)) # by name
print(book_room("Ada", breakfast=True, nights=3)) # mixed
# Named arguments may be reordered; positional ones may not.
print(book_room(3, "Ada", True))That last line runs happily and produces nonsense — the classic argument-order
bug. Naming arguments at the call site is free insurance, especially for
booleans: book_room("Ada", 3, breakfast=True) says something that True
alone does not.
*args — any number of positional arguments
def total(*numbers):
"""Sum any number of arguments."""
print("received:", numbers, type(numbers))
return sum(numbers)
print(total(1, 2))
print(total(1, 2, 3, 4, 5))
print(total())*numbers gathers every extra positional argument into a tuple. The name is up
to you; args is simply the convention.
**kwargs — any number of named arguments
def describe(**details):
print("received:", details)
for key, value in details.items():
print(f" {key}: {value}")
describe(name="Ada", born=1815, field="mathematics")**details gathers named arguments into a dictionary. Together, *args and
**kwargs let a function accept anything — which is how decorators and wrapper
functions work.
def report(title, *items, separator=", ", **meta):
print(f"{title}: {separator.join(items)}")
if meta:
print(" meta:", meta)
report("Fruit", "apple", "pear", "fig")
report("Fruit", "apple", "pear", separator=" | ", source="market", fresh=True)The order in a definition is fixed: normal parameters, then *args, then
keyword-only parameters, then **kwargs.
Unpacking at the call site
The same two stars work in reverse — spreading a collection into arguments.
def book_room(guest, nights, breakfast):
return f"{guest}: {nights} nights, breakfast={breakfast}"
booking_values = ["Grace", 2, False]
print(book_room(*booking_values)) # list -> positional arguments
booking = {"guest": "Alan", "nights": 4, "breakfast": True}
print(book_room(**booking)) # dict -> keyword argumentsThis is why you see f(*args, **kwargs) everywhere: it means “pass along
whatever I was given, untouched”.
Mutable arguments
Arguments are passed by reference. Rebinding a name inside a function is local; mutating the object is visible to the caller.
def rebind(items):
items = ["new", "list"] # only the local name changes
return items
def mutate(items):
items.append("added") # the caller's list really changes
return items
original = ["a", "b"]
rebind(original)
print("after rebind:", original)
mutate(original)
print("after mutate:", original)Neither is wrong, but a function that quietly edits its arguments should say so
in its name — sort_in_place(rows), not get_rows(rows). When in doubt, copy
the input and return a new object.
A flexible formatter
Write make_tag(name, *contents, **attributes) that builds an HTML-ish tag.
make_tag("p", "Hello", "world", class_="intro") should print:
<p class="intro">Hello world</p>Join the contents with spaces, and build each attribute as key="value"
(strip any trailing underscore from the key, since class is a reserved word).
def make_tag(name, *contents, **attributes):
# Your code here
return ""
print(make_tag("p", "Hello", "world", class_="intro"))
print(make_tag("h1", "Title"))Show one solution
def make_tag(name, *contents, **attributes):
"""Build a simple HTML tag from contents and attributes."""
parts = ""
for key, value in attributes.items():
parts += f' {key.rstrip("_")}="{value}"'
inner = " ".join(contents)
return f"<{name}{parts}>{inner}</{name}>"
print(make_tag("p", "Hello", "world", class_="intro"))
print(make_tag("h1", "Title"))This is close to how real templating helpers are written — *contents for
however many children the caller has, **attributes for however many
attributes.
What you learned
- Arguments bind by position, or by name if you name them at the call site.
*argscollects extra positional arguments into a tuple.**kwargscollects extra named arguments into a dictionary.*and**at a call site spread a sequence or mapping into arguments.- Mutating an argument changes the caller’s object; rebinding does not.