A piece of behavior needs a name so other code can call it without knowing how it works. A function is that name, with a parameter list that says what to pass.
The def keyword defines a function. After
def come the function name, the parameter list, and
a colon that begins the function body:
# a_function.py
def a_function(response):
val = 0
if response == "yes":
print("affirmative")
val = 1
print("continuing...")
return val
print(a_function("no"))
#: continuing...
#: 0
print(a_function("yes"))
#: affirmative
#: continuing...
#: 1response is a
parameter, the name the function gives to what it
receives. The value a call passes, "no" or
"yes", is an argument.
A string literal that is the first
statement under def becomes the function’s
docstring, stored on __doc__:
# documented_function.py
def greet(name):
"""Return a greeting for name."""
return f"Hello, {name}!"
print(greet("Ann"))
#: Hello, Ann!
print(greet.__doc__)
#: Return a greeting for name.A docstring documents the function
for a reader or a tool, not for the interpreter, which stores
the text and otherwise ignores it. Metaprogramming
reads it back with inspect.getdoc().
The signatures so far give the function name and the parameter names, with no argument types or return types. Python is dynamically typed, so it reports a type error at runtime rather than at compile time:
# add.py
from exceptions import expect
def add(arg1, arg2):
return arg1 + arg2
print(add(42, 47))
#: 89
print(add("spam ", "eggs"))
#: spam eggs
expect(TypeError, add, 42, "spam")
#: [TypeError] unsupported operand type(s) for +: 'int' and
#: 'str'A call succeeds as long as each operation in the function
body accepts the arguments it receives. The call binds each
argument to its parameter and accepts any type, so the failure
comes from + inside the function body.
expect()
is a helper that many of this book’s listings import. It calls
its second argument, passing along the arguments that follow,
and prints the exception that call raises.
A function can also return a different type from one call to the next:
# flexible_args_and_returns.py
def flexible_args_and_returns(arg):
if arg == 1:
return "Hello"
if arg == "one":
return 2
print(flexible_args_and_returns(1))
#: Hello
print(flexible_args_and_returns("one"))
#: 2
print(flexible_args_and_returns(2))
#: NoneEvery Python function returns a value. A function that
finishes without a return returns
None, and so does a bare return.
A return with several expressions produces a
tuple, which the caller usually unpacks:
def minmax(values):
return min(values), max(values)
low, high = minmax([3, 1, 4])
The commas build the tuple, so the function returns one
object. min_max() in Containers
returns its pair the same way.
Parameters can have default values. Keyword arguments let callers pass arguments by name, in any order. A call that names its arguments documents itself:
# default_args.py
def connect(host, port=5432, timeout=30):
return f"{host}:{port} (timeout {timeout}s)"
print(connect("db.example.com")) # Uses both defaults
#: db.example.com:5432 (timeout 30s)
# Skip to a keyword
print(connect("db.example.com", timeout=5))
#: db.example.com:5432 (timeout 5s)
# Any order by name
print(connect(port=80, host="web.example.com"))
#: web.example.com:80 (timeout 30s)A caller can pass any parameter by name, including a required
one such as host, which the last call names.
At the call site, write every
keyword argument after the positional ones.
connect(port=80, "web.example.com") is a
SyntaxError:
positional argument follows keyword argument. The
grammar makes one exception. A * unpacking, which
Unpacking Arguments
introduces, can follow a keyword argument, as in
f(x=1, *rest). Its values still bind first, as
positional arguments, so writing every keyword argument last
keeps the call in the order Python binds it.
Every required parameter must come
before the first parameter with a default.
def f(a=1, b): is a SyntaxError:
parameter without a default follows parameter with a default.
Keyword-only
parameters are exempt. def f(*, a=1, b): is
legal, because the caller names b.
Python evaluates a default value once, when it executes the
def, so every call shares that one object. A
mutable default therefore keeps the changes one call makes, and
the next call sees them:
# mutable_default.py
def bad_append(item, target=[]): # The same list every call
target.append(item)
return target
print(bad_append(1))
#: [1]
print(bad_append(2)) # Surprise, the default kept the 1
#: [1, 2]
print(bad_append.__defaults__)
#: ([1, 2],)
def good_append(item, target=None):
if target is None:
target = [] # A fresh list each call
target.append(item)
return target
print(good_append(1))
#: [1]
print(good_append(2))
#: [2]The function object keeps its
default values in the tuple __defaults__. Both
bad_append() calls append to the same list inside
that tuple. The default looks like an expression each call
evaluates, but Python evaluates it once, at the
def.
The trap follows from how Python passes arguments. A
parameter is another name bound to the caller’s object, the
binding that Variables
and References describes. Every bad_append()
call that uses the default binds target to the one
list Python builds at the def, so each call mutates
the list the next call uses.
A caller that passes a mutable object sees the changes the function makes:
# mutating_arguments.py
def append_all(target, extras):
target.extend(extras)
mine = [1, 2]
append_all(mine, [3, 4])
print(mine) # The caller's list changed
#: [1, 2, 3, 4]
def rebind(target):
target = ["replaced"] # Rebinds the local name only
print(target)
rebind(mine)
#: ['replaced']
print(mine)
#: [1, 2, 3, 4]append_all() calls a method on the object the
caller passed, so the caller sees the change.
rebind() assigns to the parameter, so the local
name points at a new list and the caller’s list stays as it
was.
good_append() builds a fresh list on every call
that uses the default. Any function that mutates a parameter
with a default must do the same. If the function reads the
parameter and leaves it as it was, use an immutable default such
as an empty tuple. Every call shares that tuple, and the sharing
is safe because a tuple is immutable:
# immutable_default.py
def show(items=()):
for item in items:
print(item)
print(f"({len(items)} items)")
show()
#: (0 items)
show(["a", "b"])
#: a
#: b
#: (2 items)With type hints, such a parameter reads:
items: Sequence[str] = ()
The None default in
good_append() is a sentinel: a value
chosen to mean “the caller passed nothing” rather than to serve
as data. Test the parameter with is None rather
than truthiness. if not target: also discards an
empty list the caller passed on purpose.
None works there because every real
target is a list. When None is a valid
argument, you need a distinct sentinel. Python 3.15 (PEP 661) adds a
sentinel built-in that creates a unique value that
prints as the name you give it:
# sentinel_default.py
MISSING = sentinel("MISSING")
def get(data, key, default=MISSING):
try:
return data[key]
except KeyError:
if default is MISSING:
return MISSING # Normally re-raises here
return default
prefs = {"volume": 3, "mute": None}
print(get(prefs, "volume"))
#: 3
print(get(prefs, "mute")) # None is a real stored value
#: None
print(get(prefs, "theme"))
#: MISSING
print(get(prefs, "theme", "dark"))
#: darkprefs stores mute as
None, so a None from
get() cannot also mean “no such key.” With the
MISSING sentinel, get() tells the two
cases apart. Because get() tests
default against MISSING, a caller can
also pass None as a real default.
Create a sentinel once and share that name. Two
sentinel() calls with the same name build two
different objects, so
default is sentinel("MISSING") compares against a
second object and is always false.
Code written before 3.15 builds its sentinel as
MISSING = object(). A bare object() is
unique, so is tells it apart from every other
value. It prints as <object object at 0x...>,
though, where a sentinel() value prints its
name.
A function can read a module-level
name, but assigning to that name anywhere in the function makes
it local for the whole function. Python decides which names are
local when it compiles the function body, so an assignment on
the last line of the body has the same effect as one on the
first. global tells Python to rebind the
module-level name:
# function_scope.py
count = 0
def read_only():
print(count)
def reads_later():
print(limit)
def rebinds():
# A local, unrelated to the module-level count
count = 99
print(count)
def writes_global():
global count
count += 1
limit = 10
read_only()
#: 0
reads_later()
#: 10
rebinds()
#: 99
print(count)
#: 0
writes_global()
print(count)
#: 1+= is an assignment
too. If you drop the global from
writes_global(), the compiler marks
count local for the whole function. The read half
of count += 1 then looks up a local that has no
value yet. A call to writes_global() raises an
UnboundLocalError. global governs
rebinding, not reading, so read_only() needs no
declaration.
reads_later() names limit, which
the module binds after the def. Python looks up a
module-level name each time the body reads it, not when
def runs. The module binds limit
before it calls reads_later(), so the lookup
succeeds.
Each call to a function gets its own execution
frame, the object Python creates to hold that call’s local
variables and its position in the body. While the call runs, the
frame is an entry on the call stack, the call’s stack
frame, and Python discards it when the function returns, so
the local count in rebinds() exists
only during a call to rebinds(). A generator’s
frame outlives a yield, and Generators
shows what that makes possible.
Closures
covers nonlocal, which rebinds a name in an
enclosing function the way global rebinds a
module-level name.
A function that rebinds a global couples every caller to that shared, mutable state. As Pure Functions shows, to understand one call you must trace every call before it. Effect Management classifies rebinding a global as a side effect and reading one as a side cause.
A *args parameter collects extra positional
arguments into a tuple, and **kwargs collects extra
keyword arguments into a dictionary:
# var_args.py
def report(label, *values, **options):
print(label, values, options)
report("nums", 1, 2, 3)
#: nums (1, 2, 3) {}
report("point", 3, 4, color="red", size=10)
#: point (3, 4) {'color': 'red', 'size': 10}The names args and kwargs are
convention. The * and ** do the
collecting, so *values and **options
behave like *args and **kwargs.
* and ** also work in the other
direction. At a call site, * unpacks any iterable
into separate positional arguments, and ** unpacks
a dictionary into keyword arguments.
# unpacking_arguments.py
def f(a, b, c):
print(a, b, c)
x = [1, 2, 3]
f(*x)
#: 1 2 3
f(*range(4, 7))
#: 4 5 6
d = {"c": 2.72, "a": 3.14, "b": 1.62}
f(**d)
#: 3.14 1.62 2.72
def report(label, *values, **options):
print(label, values, options)
nums = (1, 2, 3)
opts = {"color": "red", "size": 10}
report("point", *nums, **opts)
#: point (1, 2, 3) {'color': 'red', 'size': 10}
def trace(func, *args, **kwargs):
print("calling", func.__name__)
return func(*args, **kwargs)
trace(report, "point", *nums, **opts)
#: calling report
#: point (1, 2, 3) {'color': 'red', 'size': 10}f(*range(4, 7)) unpacks a range,
which computes its values as * consumes them.
d lists its keys out of order, yet
f(**d) prints the values in parameter order,
because ** binds each value to the parameter its
key names.
A call can use several * and **
operands, mixed with ordinary arguments:
f(*[1], *(2, 3)) and
f(1, **{"b": 2}, **{"c": 3}) each print
1 2 3. Containers uses the
same stars to spread iterables into a list,
[*evens, *odds], and to merge dictionaries,
{**a, **b}. A call is stricter than a dictionary
literal about a repeated key. {**a, **b} keeps the
later value, while a call whose two ** operands
share a key raises a TypeError.
Because collecting and unpacking are inverses, a function can
collect arguments beyond its named parameters and pass them on
as they arrived. trace() has the standard shape of
a wrapper: it accepts any call and forwards it.
expect() has the same shape. It receives a function
and the arguments for it, and makes the call. Decorators builds on
that forwarding.
A function is an object
like any other, so you can pass report to
trace() as an argument.
A forwarded **kwargs can name a parameter that
the wrapped function also receives another way. When a key in
the unpacked dictionary names a parameter that a positional
argument fills, Python raises a TypeError:
# forwarding_collision.py
from exceptions import expect
def report(label, *values, **options):
print(label, values, options)
def trace(func, *args, **kwargs):
print("calling", func.__name__)
return func(*args, **kwargs)
nums = (1, 2, 3)
opts = {"label": "oops", "color": "red"}
expect(TypeError, trace, report, *nums, **opts)
#: calling report
#: [TypeError] report() got multiple values for argument
#: 'label'opts carries a "label" key, which
trace() forwards as label=. The same
func(*args, **kwargs) call passes the items of
nums positionally, so report()’s first
parameter, label, also receives 1.
Each parameter takes one value. trace() never reads
func’s signature, so the clash first appears when
trace() calls report(). Because Python
raises TypeError there, calling report
prints first.
Two markers in a parameter list control how callers may pass arguments. The markers also decide which parameter names you can later rename without breaking a call. A parameter’s name is part of the contract when a caller can write it, and stays outside the contract when the caller must pass by position.
A / ends the positional-only
parameters. You must pass every parameter before it by position.
A * begins the keyword-only parameters.
You must pass every parameter after it by name. A
*args parameter also begins the keyword-only
parameters. It collects every remaining positional argument, so
a parameter declared after it can arrive by name alone.
# param_markers.py
from exceptions import expect
def divide(a, b, /):
return a / b
print(divide(10, 2))
#: 5.0
expect(TypeError, divide, a=10, b=2) # type: ignore
#: [TypeError] divide() got some positional-only arguments
#: passed as keyword arguments: 'a, b'
def make_user(name, *, admin=False):
return f"{name} (admin={admin})"
print(make_user("Bob"))
#: Bob (admin=False)
print(make_user("Sue", admin=True))
#: Sue (admin=True)
expect(TypeError, make_user, "Sue", True) # type: ignore
#: [TypeError] make_user() takes 1 positional argument but 2
#: were given
def tally(label, *values, total=False):
print(label, values, total)
tally("nums", 1, 2, True) # [1]
#: nums (1, 2, True) False
tally("nums", 1, 2, total=True)
#: nums (1, 2) TrueThe type checker reports the divide() and
make_user() mistakes before the code runs, so each
of those lines carries a # type: ignore saying the
misuse is deliberate.
The True in the
tally() call at [1] joins
values like any other positional argument.
A signature can use every form at once, in one set order:
positional-only, positional-or-keyword, *args,
keyword-only, **kwargs.
# all_markers.py
def f(a, /, b, *args, c, **kwargs):
print(a, b, args, c, kwargs)
f(1, 2, 3, 4, c=5, d=6)
#: 1 2 (3, 4) 5 {'d': 6}
f(1, b=2, c=5)
#: 1 2 () 5 {}In the standard library, many built-in functions and methods
take positional-only parameters, such as
dict.get(key, default=None, /). Because a
positional-only parameter’s name stays outside the method’s
contract, a subclass that overrides the method can rename the
parameter. The type checker accepts the rename.
The / marker also lets **kwargs
collect a keyword argument that shares a positional-only
parameter’s name. With
def trace(func, /, *args, **kwargs),
kwargs collects a caller’s func=
keyword, and trace() passes it on to the wrapped
function. Without the /, the same keyword gives
trace()’s own func a second value, so
the call raises a TypeError.
Among the built-ins,
max() and min() take a keyword-only
default, which they return for an empty iterable in
place of raising a ValueError. With
max(scores, default=0), the call returns
0 for an empty scores, so the empty
case takes one argument instead of a
try/except.
A lambda is a small anonymous function you write
as a single expression. Use one to pass behavior to functions
such as sorted(), which accepts a key
function, calls it on each element, and orders by the results.
When an existing function computes the key, pass that function,
as key=len does. Write a lambda when the key is an
expression of your own, such as ordering by a word’s last
letter:
# lambdas.py
words = ["banana", "kiwi", "apple", "fig"]
print(sorted(words, key=len))
#: ['fig', 'kiwi', 'apple', 'banana']
print(sorted(words, key=lambda w: w[-1]))
#: ['banana', 'apple', 'fig', 'kiwi']
square = lambda n: n * n # Usually prefer def
print(square.__name__)
#: <lambda>square = lambda n: n * n gives up the anonymity
that is a lambda’s point. The assignment binds the name
square and leaves the function’s
__name__ as <lambda>, so a
traceback shows <lambda> where a
def shows square.
Unlike the body of an anonymous function in many other
languages, a lambda body must be a single expression. If the
body needs a statement, such as a for loop or a
try, write a def.
For a key that reads an index or
an attribute, operator.itemgetter() and
operator.attrgetter() build that key function
without a lambda.
sorted(words, key=operator.itemgetter(-1)) replaces
key=lambda w: w[-1] in lambdas.py. For a key that
calls a method on each element,
operator.methodcaller("lower") replaces
lambda w: w.lower(), and when every element is a
str, so does str.lower. Write a lambda
for any other key expression.
Try each exercise before opening its solution.
mutable_default.py, call
bad_append(3) a third time and predict the result
before checking it. Then change bad_append()’s
default from [] to () and explain why
that change alone does not fix bad_append() (hint:
target.append(item) on a tuple).sentinel_default.py,
replace return MISSING with a bare
raise, so a missing key with no default re-raises
KeyError. Confirm that
get(prefs, "theme") raises a KeyError
and that get(prefs, "theme", None) returns
None. Explain why default=None cannot
serve as the sentinel in this function.param_markers.py, add a
parameter label="result" to divide(),
keyword-only, so print(divide(10, 2, label="half"))
shows half: 5.0. Confirm that
divide(10, 2, "half"), passing label
positionally, is now a TypeError.report() from var_args.py so it accepts
a total=False keyword-only flag that, when true,
also prints sum(values). Confirm
report("nums", 1, 2, 3, total=True) prints the
sum.apply_twice(func, value) that returns
func(func(value)), then call it with a lambda that
appends "!" to a string. Predict the result of
apply_twice(lambda s: s + "!", "hi") before running
it.args = ("point", 3, 4) and
opts = {"color": "red"}, call report()
from var_args.py so it prints
point (3, 4) {'color': 'red'}, passing both
containers without naming their contents.describe(name, /, **facts) that prints
name followed by each keyword argument as
key=value, one per line. Confirm that
describe(name="Bob") is a TypeError,
and explain which marker caused it.function_scope.py, delete
the global count line from
writes_global() and predict what a call raises
before running it. Then restore it, and instead add
print(count) as the first line of
rebinds(). Explain why a call to
rebinds() also raises an
UnboundLocalError, although the assignment to
count comes after the print.clear_by_assignment(target), which
assigns target = [], and
clear_by_method(target), which calls
target.clear(). Pass the same list to each, and
predict which call empties the caller’s list before running
them.report() and trace() from unpacking_arguments.py,
predict what trace(report, "point", func="f") does,
then run it. Add a / after func in
trace()’s parameter list and run the call again.
Explain where func="f" goes each time.