You already know that you can call a function by passing values in order, or by naming the parameter. Python gives us two extra tools to make signatures safer: positional-only and keyword-only parameters.
Keyword-only with *
Put a bare
* in the signature. Every parameter after it must be passed by keyword.def connect(host, *, timeout=10, retries=3):
return f'{host} (timeout={timeout}, retries={retries})'
print(connect('db.local'))
# db.local (timeout=10, retries=3)
print(connect('api.io', timeout=5))
# api.io (timeout=5, retries=3)connect('db', 99) now raises TypeError. That stops the classic bug of passing a number for one flag and accidentally shifting it into another. The caller has to spell out which option they mean.Positional-only with /
def area(width, height, /):
return width * height
print(area(4, 5)) # 20
# print(area(width=4, height=5)) -> TypeError/ says: parameters before it are matched by position only. The names width and height become implementation details — you can rename them later without breaking any caller that does area(4, 5).Reading real signatures
# How the Python documentation writes the signature of sorted():
# sorted(iterable, /, *, key=None, reverse=False)
# iterable comes before the /, so it is positional-only;
# key and reverse come after the *, so they are keyword-only.
iterable is positional-only. key and reverse are keyword-only with defaults. So sorted([3,1], None) fails — you must write sorted([3,1], key=None). This shape keeps the most important argument (the data itself) first and unnamed, and makes every option say its name.Unpacking when calling
def add(a, b):
return a + b
def connect(host, *, timeout=10, retries=3):
return (host, timeout, retries)
nums = [2, 5]
opts = {'timeout': 7}
print(add(*nums)) # 7 -> add(2, 5)
print(connect('x', **opts))# ('x', 7, 3)