Python

Modern syntax — the walrus operator and positional/keyword-only parameters

Three small pieces of syntax that don't add new capability so much as let existing patterns be written more directly — an assignment that's also an expression, and two markers that make a function signature say exactly how it's meant to be called.

Intermediate

3 min read

The walrus operator :=: assign and use a value in the same expression

# WITHOUT the walrus — the length is computed and checked separately
data = get_input()
if len(data) > 100:
    print(f"Got {len(data)} characters")   # computed a SECOND time
 
# WITH the walrus — computed once, assigned, AND used in the same expression
if (n := len(data)) > 100:
    print(f"Got {n} characters")

:= (added in Python 3.8) assigns a value to a name and evaluates to that value at the same time, inside a larger expression — something a plain = statement can't do, since a plain assignment isn't itself an expression with a value. The practical win is avoiding computing (or looking up) the same value twice: without it, either the length gets computed twice, or it has to be assigned on its own line just to be reused one line later.

# Very common pattern: read-and-check in a loop condition
while (line := file.readline()):
    process(line)
 
# Filtering AND keeping the computed value, in a list comprehension
results = [y for x in data if (y := expensive_transform(x)) is not None]

The while (line := file.readline()) pattern reads a line and checks it's non-empty in one condition, instead of the older line = file.readline() followed by a separate while line: — collapsing setup-then-loop into the loop condition itself. Inside a comprehension, := lets a filtered value be reused in the output expression without recomputing it — expensive_transform(x) runs exactly once per item, not once to filter and again to produce the result.

Positional-only parameters: /

def divide(a, b, /):
    return a / b
 
divide(10, 2)          # 5.0 — fine
divide(a=10, b=2)       # TypeError — a and b can ONLY be passed positionally

A / in a function signature marks every parameter before it as positional-only — it can never be passed by keyword, even though it has a name internally. This matters for API design: it lets a function's parameter names be freely renamed later without breaking any caller, since callers were never allowed to depend on those names in the first place — math.pow(x, y) and similar built-ins use exactly this to keep their parameter names as an implementation detail, not a public contract.

Keyword-only parameters: *

def create_user(name, *, is_admin=False, is_active=True):
    ...
 
create_user("Ada", True)              # TypeError — is_admin can't be passed positionally
create_user("Ada", is_admin=True)      # correct — must be named

A bare * in a signature marks every parameter after it as keyword-only — it can never be passed positionally, only by name. This exists for the opposite reason / does: to force clarity at the call site for parameters where position alone would be genuinely ambiguous or dangerous to get wrong — create_user("Ada", True) doesn't say what True even means without looking up the function; create_user("Ada", is_admin=True) is unambiguous by construction, and a typo'd argument order becomes a TypeError instead of a silent logic bug.

Both markers can appear in the same signature — everything before / is positional-only, everything after * is keyword-only, and anything in between can be passed either way, exactly matching how many real standard-library functions are actually declared.

Further reading

Check your understanding

A quick comprehension check — not tracked, not graded, just for you.

1. What does `if (n := len(data)) > 100:` do that `if len(data) > 100:` followed by using len(data) again doesn't?

2. In `def divide(a, b, /): ...`, what does the / do?

3. In `def create_user(name, *, is_admin=False): ...`, what does the * do?

4. Why might a function force is_admin to be keyword-only rather than allowing it positionally?