Python Notes : More Control Flow Tools (Functions)
Defining Functions
function that writes the Fibonacci series to an arbitrary boundary
>>> def fib(n):    # write Fibonacci series less than n
...    """Print a Fibonacci series less than n."""
...    a, b = 0, 1
...    while a < n:
...        print(a, end=' ')
...        a, b = b, a+b
...    print()
...
>>> # Now call the function we just defined:
>>> fib(2000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
keyword def introduces a function definition
followed by function name and the parenthesized list of formal parameters

first statement of the function body can optionally be a string literal
string literal is the function's documentation string, or docstring

a new symbol table used for the local variables
all variable assignments are stored this symbol table
variable references by looking in

  • the local symbol table
  • the local symbol tables of enclosing functions
  • the global symbol table
  • the table of built-in names
global variables and variables of enclosing functions cannot be directly assigned a value within a function
assumption : helps prevent side-effects
exceptions are
  • variables, named in a global statement
  • variables of enclosing functions, named in a nonlocal statement
the global and nonlocal keywords are covered later in the tutorial

arguments are passed using call by value aka by reference, not by value
all Python functions return a value with default None
function that returns a list of the numbers of the Fibonacci series

>>> def fib2(n):  # return Fibonacci series up to n
...    """Return a list containing the Fibonacci series up to n."""
...    result = []
...    a, b = 0, 1
...    while a < n:
...        result.append(a)    # see below
...        a, b = b, a+b
...    return result

>>> f100 = fib2(100)    # call it
>>> f100                # write the result
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
the return statement returns a value
return without an expression argument returns None
function without a return statement returns None
the statement result.append(a) calls a method of the list object result
Default Argument Values
default arguments
def ask_ok(prompt, retries=4, reminder='Please try again!'):
    while True:
        reply = input(prompt)
        if reply in {'y', 'ye', 'yes'}:
            return True
        if reply in {'n', 'no', 'nop', 'nope'}:
            return False
        retries = retries - 1
        if retries < 0:
            raise ValueError('invalid user response')
        print(reminder)
in keyword tests whether or not a sequence contains the specified value
default values are evaluated at the point of function definition in the defining scope
i = 5

def f(arg=i):
    print(arg)

i = 6
f() # prints 5
print(i) # prints 6
Important warning : The default value is evaluated only once
this makes a difference when the default is a mutable object such as a list, dictionary, or instances of most classes
this function accumulates the arguments passed to it on subsequent calls
def f(a, L=[]):
    L.append(a)
    return L

print(f(1)) # prints [1]
print(f(2)) # prints [1, 2]
print(f(3)) # prints [1, 2, 3]
to defeat the default behavior of sharing between subsequent calls
def f(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L
Keyword Arguments
functions can be called using keyword arguments of the form argName=value
def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
    print("-- This parrot wouldn't", action, end=' ')
    print("if you put", voltage, "volts through it.")
    print("-- Lovely plumage, the", type)
    print("-- It's", state, "!")
parrot can be called multiple ways
parrot(1000)                                          # 1 positional argument
parrot(voltage=1000)                                  # 1 keyword argument
parrot(voltage=1000000, action='VOOOOOM')             # 2 keyword arguments
parrot(action='VOOOOOM', voltage=1000000)             # 2 keyword arguments
parrot('a million', 'bereft of life', 'jump')         # 3 positional arguments
parrot('a thousand', state='pushing up the daisies')  # 1 positional, 1 keyword
below arguments is a tuple and keywords is a dictionary
the * and ** unpack the two arguments
def cheeseshop(kind, *arguments, **keywords):
    print("-- Do you have any", kind, "?")
    print("-- I'm sorry, we're all out of", kind)
    for arg in arguments:
        print(arg)
    print("-" * 40)
    for kw in keywords:
        print(kw, ":", keywords[kw])
Special Parameters
arguments may be passed to a Python function either by position or explicitly by keyword
can restrict the way arguments can be passed
developer can look at the function definition to determine how args are passed
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
      -----------    ----------     ----------
        |             |                  |
        |        Positional or keyword   |
        |                                - Keyword only
         -- Positional only
Positional-or-Keyword Arguments
if / and * are not present in the function definition, arguments may be passed to a function by position or by keyword
Positional-Only Parameters
if positional-only the parameter order matters
positional-only parameters are placed before a / (forward-slash)
the / is used to logically separate the positional-only parameters from the rest of the parameters
parameters following the / may be positional-or-keyword or keyword-only
Keyword-Only Arguments
to mark parameters as keyword-only place an * in the arguments list just before the first keyword-only parameter
Function Examples
arguments may be passed by position or keyword
>>> def standard_arg(arg):
...    print(arg)
restricted to only use positional parameters
>>> def pos_only_arg(arg, /):
...    print(arg)
only allows keyword arguments
>>> def kwd_only_arg(*, arg):
...    print(arg)
uses all three calling conventions in the same function definition
>>> def combined_example(pos_only, /, standard, *, kwd_only):
...    print(pos_only, standard, kwd_only)
Possible Collisions
function will not return True as 'name' will bind to the first argument
def foo(name, **kwds):
    return 'name' in kwds
invocation with 'name' as key in dictionary fails
>>> foo(1, **{'name': 2})
Traceback (most recent call last):
  File "", line 1, in 
    TypeError: foo() got multiple values for argument 'name'
    >>>
using the / prevents collision
>>> def foo(name, /, **kwds):
...    return 'name' in kwds

>>> foo(1, **{'name': 2})
True
Recap
guidance
  • use positional-only if you want the name of the parameters to not be available to the user
    useful when parameter names have no real meaning
    useful to enforce the order of the arguments when the function is called
    useful when taking some positional parameters and arbitrary keywords
  • use keyword-only when names have meaning and the function definition is more understandable by being explicit with names
    prevents users relying on the position of the argument being passed
  • for an API use positional-only
    prevents breaking API changes if the parameter's name is modified in the future
Arbitrary Argument Lists
function can be called with an arbitrary number of arguments
arguments will be wrapped up in a tuple
def write_multiple_items(file, separator, *args):
    file.write(separator.join(args))
usually variadic arguments will be last in the list of formal parameters
any formal parameters which occur after the *args parameter are ‘keyword-only' arguments
>>> def concat(*args, sep="/"):
...    return sep.join(args)
...
>>> concat("earth", "mars", "venus")
'earth/mars/venus'
>>> concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'
Unpacking Argument Lists
can use tuple as argument if it is unpacked
>>> list(range(3, 6)) # normal call with separate arguments
[3, 4, 5]
>>> args = [3, 6]
>>> list(range(*args)) # call with arguments unpacked from a list
[3, 4, 5]
can use dictionary as argument if it is unpacked
>>> def parrot(voltage, state='a stiff', action='voom'):
...    print("-- This parrot wouldn't", action, end=' ')
...    print("if you put", voltage, "volts through it.", end=' ')
...    print("E's", state, "!")
...
>>> d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
>>> parrot(**d)
-- This parrot wouldn't VOOM if you put four million volts through it. E's bleedin' demised !
Lambda Expressions
small anonymous functions can be created with the lambda keyword
>>> def make_incrementor(n):
...    return lambda x: x + n
...
>>> f = make_incrementor(42)
>>> f(0)
42
>>> f(1)
43
can pass lambda function as an argument
>>> pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
>>> pairs.sort(key=lambda pair: pair[1])
>>> pairs
[(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]
Documentation Strings
first line should always be a short, concise summary of the object's purpose
no need to explicitly state the object's name or type
exception is when name is a verb describing the function operation
line should start with capital letter and end with a period

if there are more lines the second one should be blank
following lines should provide details of calling conventions, side effects, etc.
multi-line docstring

>>> def my_function():
...    """Do nothing, but document it.
...
...    No, really, it doesn't do anything.
...    """
...    pass
...
>>> print(my_function.__doc__)
Do nothing, but document it.

    No, really, it doesn't do anything.
Function Annotations
function annotations are completely optional metadata information about the types used by user-defined functions
annotations are stored in the __annotations__ attribute of the function as a dictionary
annotations have no effect on any other part of the function
>>> def f(ham: str, eggs: str = 'eggs') -> str:
...    print("Annotations:", f.__annotations__)
...    print("Arguments:", ham, eggs)
...    return ham + ' and ' + eggs
...
>>> f('spam')
Annotations: {'ham': <class 'str'>, 'return': <class 'str'>, 'eggs': <class 'str'>}
Arguments: spam eggs
'spam and eggs'
Intermezzo: Coding Style
PEP 8 has emerged as the style guide that most projects adhere to
  • use 4-space indentation and no tabs
    tabs introduce confusion
  • wrap lines so that they don't exceed 79 characters
  • use blank lines to separate functions and classes, and larger blocks of code inside functions
  • when possible put comments on a line of their own
  • use docstrings
  • use spaces around operators and after commas, but not directly inside bracketing constructs
  • name classes and functions consistently
    the convention is to use UpperCamelCase for classes
    lowercase_with_underscores for functions and methods
previous    index    next