Default arguments
Default parameter values are evaluated once when the function is defined, not on each call.
def func(x, things=[]):
things.append(x)
return things
func(1) # [1]
func(2) # [1, 2]
func(3) # [1, 2, 3]
Best practice:
- DO use immutable objects (None, number, string) as default argument values
Variadic arguments
Below function accept variable number of arguments.
def compute_product(first_number, *rest):
product = first_number
for x in rest:
product *= x
return product
compute_product(10, 20) # -> 200
compute_product(2, 3, 4, 5) # -> 120
All extra arguments are placed in rest variable as a tuple.
Keyword arguments
Below, debug variable must be supplied as keyword argument like read_data('foo.txt', debug=True).
def read_data(file_name, *, debug):
statements
Variadic keyword arguments
If the last argument of a function definition has ** prefix, all the additional keyword arguments are placed in a dictionary and passed to the function. The order of items in the dictionary is guaranteed to match the order in which keyword arguments were provided.
def make_table(data, **params):
# Get configuration parameters from params (a dict)
fgcolor = params.pop('fgcolor', 'black')
bgcolor = params.pop('bgcolor', 'white')
width = params.pop('width', None)
...
# No more options
if params:
raise TypeError(f"Unsupported configuration options: {list(params)}")
make_table(items, fgcolor='blue', bgcolor='white', border=1, borderstyle='grooved', cellpadding=0, width=400)
All inputs
Using both * and ** let a function accept any combination of inputs.
# Accept variable number of positional or keyword arguments
def func(*args, **kwargs):
# args is a tuple of positional args
# kwargs is a dict of keyword args
Commonly used to write wrappers, decorators, proxies.
Below, parse_file wraps parse_lines providing a file as the source of the lines to parse. The parse_file does not need to know what args parse_lines need. If a new arg is added to parse_line, that automatically works with parse_file.
def parse_lines(lines, separator=',', types=(), debug=False):
for line in lines:
...
statements
...
def parse_file(filename, *args, **kwargs):
with open(filename, 'rt') as file:
return parse_lines(file, *args, **kwargs)
Positional-only arguments
All args before / must be positional.
def func(x, y, /):
pass
func(1, 2) # Ok
func(1, y=2) # Error
A useful way to avoid potential name clashes between arguments.
import time
def duration(*, seconds, minutes, hours):
return seconds + 60 * minutes + 3600 * hours
def after(seconds, func, /, *args, **kwargs):
time.sleep(seconds)
return func(*args, **kwargs)
after(5, duration, seconds=20, minutes=3, hours=2)
duration() and after(), both have seconds as arg name, but after() forces it to be positional arg. As a result, on a call to after() the seconds keyword can be used for duration().
Parameter passing
Python passes the supplied objects to the function “as is” without any extra copying. The function parameters are local names that get bound to the passed input objects.
def square(items):
for i, x in enumerate(items):
items[i] = x*x # modify items in-place
a = [1, 2, 3, 4]
square(a) # a --> [1, 4, 9, 16]
def sum_squares(items):
# name "items" is now bound to the result of
# the list comprehension, it is not overwriting
# the original object "a"
items = [x*x for x in items]
return sum(items)
a = [1, 2, 3, 4]
result = sum_squares(a)
print(a) # [1, 2, 3, 4] (a is unchanged)
Functions with side-effects, like sort(), return None.
Data in a sequence or mapping can be passed to a function. As long as the function gets the required args the calls work.
def func(x, y, z):
...
s = (1, 2, 3)
# pass a sequence as args
result = func(*s)
# pass a mapping as kwargs
d = {'x': 1, 'y': 2, 'z': 3}
result = func(**d)
Leave a comment