Step 1 of 8 · Reading · ~3 min
Learn
Functions
Arguments and Scope
After this lesson you can design signatures with positional-only, keyword-only and variable-length parameters, forward arguments with * and **, avoid the shared-default bug, and predict which variable a name refers to.
In Python Fundamentals you wrote def with plain defaults and keyword calls. This lesson covers how Python matches arguments to parameters in the hard cases, and how it looks names up.
The five parameter kinds
('div', (), None, {})
('p', ('intro', 'big'), 'top', {'title': 'Hi'})
('p', ('intro', 'top'), None, {})
namebefore the/is positional-only (3.8+): the caller cannot pass it by keyword.tag(name="div")raises a TypeError (missing argument).*classescollects every extra positional argument into a tuple.idafter*classesis keyword-only: it can never be filled positionally. In the third call"top"was swallowed by*classesandidstayedNone, with no error.**attrscollects unmatched keyword arguments into a dict; a named parameter is never in it.
A bare * (as in def box(w, *, h)) makes keyword-only parameters without collecting anything.
Unpacking at the call site
In a call the stars do the reverse: * spreads an iterable into positional arguments, ** a dict into keyword arguments.
That is exactly tag("p", "intro", "big", id="top", title="Hi").
Defaults are evaluated once
A default expression runs when def executes, not on each call. With a mutable default, every call shares one object:
['a']
['a', 'b']
(['a', 'b'],)
The function object stores that list. The fix is a sentinel: default to None and build the object inside.
Now log("a") then log("b") give ['a'] then ['b']. Immutable defaults are safe.
Arguments are shared references
Mutating through a parameter changes the caller's object; rebinding the parameter does not.
Name lookup: LEGB
Names are looked up in the Local scope, any Enclosing function, the Global (module) scope, then Built-ins; the first hit wins. A name is local to a function if the function assigns to it anywhere.
This raises UnboundLocalError: balance -= x assigns, so balance is local, and it is read before it has a value. Reading a global needs no declaration; rebinding one needs global:
For a variable of an enclosing function, use nonlocal:
Without nonlocal, add raises the same UnboundLocalError. .append on a global list needs no declaration: it never rebinds the name.
Common mistakes
- A mutable default such as
notes=[]: the second call returns the first call's items, so one order's fees show up in the next. - Leaving a known option out of the signature: with
taxonly in**fees, it is one more extra and gets added as a flat fee. - Assigning to a global without
global:+=raisesUnboundLocalError; plain=silently builds a new local and the module-level value never changes. - Summing successive percentages: discounts of 10 and 5 applied one after another take 14.5% off, not 15%.
Your exercise
Write price with *discounts, a keyword-only tax, a notes parameter and **fees, and call it with * and ** unpacking. Keep a list out of the default, or fees from one order will appear on the next.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…