Legends ofPythos
Claim your name
Writing Real Programs

Positional-only and keyword-only parameters

Lesson 5 of 16

Watch the lesson1:07 · with Sigrid
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)

Your turn

0 of 3 solved

Exercise 1

+35 XP
Define send(message, *, priority='low', encrypted=False), where the bare * makes priority and encrypted keyword-only. It returns f'{message} | p={priority} e={encrypted}'. For example, send('hi') is 'hi | p=low e=False'.
def send(message):
    # add the bare * and the two keyword-only parameters
    pass

Run your code to check it against the tests.

Exercise 2

+35 XP
Define area(width, height, /), where the / makes both parameters positional-only, returning the area of the rectangle. Then define label(text, /, *, upper=False): text is positional-only, upper is keyword-only, and it returns text in capitals when upper is true, or unchanged otherwise.
def area(width, height):
    # make both positional-only
    pass




def label(text, upper=False):
    # text positional-only, upper keyword-only
    pass

Run your code to check it against the tests.

Exercise 3

+35 XP
Define configure(host, *, port=80, tls=False) returning the tuple (host, port, tls). Then call it with unpacking: spread the list positional into the call with * and the dictionary options with **, and store what it returns in result.
def configure(host):
    # port (default 80) and tls (default False) are keyword-only
    pass




positional = ['db.local']
options = {'port': 5432, 'tls': True}
result = None  # call configure, unpacking both

Run your code to check it against the tests.