Positional, keyword, default
A call passes positional arguments first and keyword arguments after. Defaults make an argument optional, and a keyword argument at the call site is documentation the reader gets for free: send(..., urgent=True) says more than send(..., True).
def send(to, subject, body="", urgent=False):
...
send("a@b.c", "Hi") # positionally
send(to="a@b.c", subject="Hi") # by keyword — order no longer matters
send("a@b.c", subject="Hi", urgent=True) # mixed: positional first
# defaults are evaluated ONCE, when the def runs — not on each call
The mutable default
The default expression is evaluated once, when the def statement runs. If it is a list, a dict, a set or anything else mutable, every call that relies on the default shares one object — and it accumulates.
The rule is simple and absolute: default to None, and build the real default inside.
def add(item, basket=[]): # the SAME list, for every call, forever
basket.append(item)
return basket
add("a") # ['a']
add("b") # ['a', 'b'] ← not a fresh basket
def add(item, basket=None): # the fix, every time
if basket is None:
basket = []
basket.append(item)
return basket
args and *kwargs
The star in a definition collects; the star at a call site spreads. It is the same symbol doing opposite jobs, and once you see that, the whole notation stops being mysterious.
def log(level, *messages, **fields):
# level one positional
# messages a TUPLE of any extra positional arguments
# fields a DICT of any extra keyword arguments
print(level, " ".join(messages), fields)
log("info", "user", "logged in", user_id=7)
# info user logged in {'user_id': 7}
args = ["a", "b"]
opts = {"user_id": 7}
log("info", *args, **opts) # unpacking, at the call site
Forcing the call to be readable
* in the parameter list means "everything after this must be named". Use it for flags and options — resize(img, True, False, True) is unreadable at the call site and unchangeable afterwards, because any caller may be depending on the order.
/ means "everything before this cannot be named", which frees you to rename those parameters later. Most standard-library functions use it.
def crop(image, /, width, height, *, keep_ratio=False):
...
# image positional ONLY — before the /
# width, height either
# keep_ratio keyword ONLY — after the *
crop(img, 100, 200, keep_ratio=True) # fine
crop(image=img, width=100, height=200) # TypeError: image is positional-only
crop(img, 100, 200, True) # TypeError: keep_ratio is keyword-only
Returning
A function with no return returns None. A return with several values returns a tuple, which the caller unpacks — and that is idiomatic Python rather than a trick.
def divide(a, b):
if b == 0:
return None, "cannot divide by zero"
return a / b, None # a tuple: parentheses optional
value, error = divide(10, 2)
def nothing():
pass # returns None — every function returns something