Python course · Part 5: Functions · lab 5 of 7
Functions worth reusing
Arguments and defaults, the default that remembers things it should not, returning rather than printing, passing a function to a function, and where a name can be seen from.
By the end you can
- Give a function arguments and sensible defaults, and avoid the mutable default trap
- Return values instead of printing them, and say why that matters
- Pass a function to another function, and use key= properly
- Explain which names a function can see and which it cannot change
Easier if you did first
step 1 of 4
Arguments, defaults, and one famous trap
A function takes arguments by position, by name, or both. def quote(pages, urgent=False) means pages is required and urgent has a default, so quote(3) and quote(3, urgent=True) both work.
Giving the name at the call site is worth doing whenever the value alone would not tell a reader what it means. quote(3, True) says nothing; quote(3, urgent=True) says everything.
Now the trap, and it is the most famous one in Python. A default value is created once, when the function is defined, not each time it is called. So def add(item, basket=[]) has exactly one list, shared by every call that does not pass its own.
The result is a function that remembers things between calls. Call it twice and the second call starts with the first call's item already in it. It looks like a haunting, and it catches everyone once.
The fix is always the same shape: default to None, and make the real thing inside.
Write the three. The second one is where the trap is.
# Haunted: one list, shared by every call.
def add(item, basket=[]):
basket.append(item)
return basket
# Fixed: a fresh list each time one is needed.
def add(item, basket=None):
if basket is None:
basket = []
basket.append(item)
return basketWhat these words mean
- def f(a, b=1)
- a is required, b has a default.
- keyword argument
- Passing by name: f(3, urgent=True).
- mutable default
- A list or dict as a default. Created once, shared forever.
- *args
- Any number of positional arguments, as a tuple.
- **kwargs
- Any number of named arguments, as a dictionary.
Easy to get wrong
def f(items=[])
def f(items=None), then items = [] inside
The default list is made once and kept for the life of the program, so calls share state. Numbers and text are safe as defaults because they cannot be changed; lists and dictionaries are not.
quote(3, True, False, 2)
quote(3, urgent=True, rush=False, revisions=2)
Nobody reading the first version can tell what those booleans mean without opening the function. Names at the call site are free documentation.
What we check
- A plain quote is pages times the page price
- Urgent adds a quarter
- The discount comes off
- Surcharge and discount work together
- A line is added
- Each call with no invoice starts fresh, which is the trap
- An invoice you pass in is added to
- Options come back sorted by name
- No options says so
- No mutable default in the signature
Nothing printed yet. Run your code.