Functions

The goal

A function allows to seperate a part of the code in a logical module that can be reused.

Questions to David Rotermund

Logic blocks need to be indented.​ Preferable with 4 spaces!

Most simple function

These three functions are equivalent:

def my_function():
    pass

def my_function():
    return

def my_function():
    return None

return leaves the current function call with the expression list (or None) as return value.

There is a return at the end, even if you don’t put it there.

def

funcdef                   ::=  [decorators] "def" funcname [type_params] "(" [parameter_list] ")"
                               ["->" expression] ":" suite
decorators                ::=  decorator+
decorator                 ::=  "@" assignment_expression NEWLINE
parameter_list            ::=  defparameter ("," defparameter)* "," "/" ["," [parameter_list_no_posonly]]
                                 | parameter_list_no_posonly
parameter_list_no_posonly ::=  defparameter ("," defparameter)* ["," [parameter_list_starargs]]
                               | parameter_list_starargs
parameter_list_starargs   ::=  "*" [parameter] ("," defparameter)* ["," ["**" parameter [","]]]
                               | "**" parameter [","]
parameter                 ::=  identifier [":" expression]
defparameter              ::=  parameter ["=" expression]
funcname                  ::=  identifier

pass

pass_stmt ::=  "pass"

pass is a null operation — when it is executed, nothing happens. It is useful as a placeholder when a statement is required syntactically, but no code needs to be executed

return

return_stmt ::=  "return" [expression_list]

return may only occur syntactically nested in a function definition, not within a nested class definition.

If an expression list is present, it is evaluated, else None is substituted.

return leaves the current function call with the expression list (or None) as return value.

When return passes control out of a try statement with a finally clause, that finally clause is executed before really leaving the function.

Return values​

No return value

As we stated above these functions all return None:

def my_function():
    pass

def my_function():
    return

def my_function():
    return None

One return value

These functions return one value:

def my_function():
    return 1

def my_function():
    return "Hello"

def my_function():
    return (1+1)/1

e.g.

def my_function():
    return 1

print(my_function())

Many return values

def my_function():
    return 2, "A", 79, 3.1314


print(my_function()) # -> (2, 'A', 79, 3.1314)
abcd = my_function() # -> (2, 'A', 79, 3.1314)
print(abcd) # -> (2, 'A', 79, 3.1314)

a, b, c, d = my_function()

print(a) # -> 2
print(b) # -> A
print(c) # -> 79
print(d) # -> 3.1314

a, b, c = my_function() # ValueError: too many values to unpack (expected 3)

Functions are objects

Functions are objects. Without () at the end, you interact with the function. With the () you use the function:

def my_function():
    return 1

print(my_function)  # -> <function my_function at 0x7f956166a520>
print(my_function())  # -> 1

Interesting, but why do I need to know this? Good question, imaginary person! We can use functions as “parameters” via arguments for other functions:

def my_function_0():
    return 3

def my_function_1():
    return 2


def my_function_2(func):
    return func() ** 2

print(my_function_2(my_function_0))  # -> 9
print(my_function_2(my_function_1))  # -> 4

Default Argument Values

We can define a default value for arguments:

def my_function(a=2, b=7, c=5, d=8):
    return a + b**2 + c**3 + d**4

print(my_function()) # -> 4272

We can (partially) overwrite the default values:

def my_function(a=2, b=7, c=5, d=8):
    return a + b**2 + c**3 + d**4

print(my_function(b=2,a=3)) # -> 4228
print(my_function(d=7))  # -> 2577
print(my_function(3,2)) # -> 4228
print(my_function(3,b=2))  # -> 4228
print(my_function(b=2,3)) # SyntaxError: positional argument follows keyword argument

In the case of my_function(3,2), the default values are replaced in the order the arguments of the function are defined. a,b,c, and finally d. When using the keyword e.g. d. Then I can overwrite only selected arguments.

Important warning – Mutable objects

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.

Deadly:

def f(a, L=[]):
    L.append(a)
    return L

print(f(1)) # -> [1]
print(f(2)) # -> [1, 2]
print(f(3)) # -> [1, 2, 3]

Correct:

def f(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L

print(f(1))  # -> [1]
print(f(2))  # -> [2]
print(f(3))  # -> [3]

Mutable arguments don’t need return values​

def f(x):
    x.append(1)

y = []
f(y)
print(y)

Arguments: Positional and Keywords

A function definition may look like:

def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
      -----------    ----------     ----------
        |             |                  |
        |        Positional or keyword   |
        |                                - Keyword only
         -- Positional only

This is the typical case:

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.

Keyword Arguments

This is an optional topic!

Functions can also be called using keyword arguments of the form kwarg=value.

Positional-Only Parameters

This is an optional topic!

If positional-only, the parameters’ order matters, and the parameters cannot be passed by keyword.

If there is no / in the function definition, there are no positional-only parameters.

Keyword-Only Arguments

This is an optional topic!

To mark parameters as keyword-only, indicating the parameters must be passed by keyword argument, place an * in the arguments list just before the first keyword-only parameter.

Arbitrary Argument Lists

def f(p1, *arguments, **keywords):
    print("p1:")
    print(p1)
    print()

    print("*arguments")
    for arg in arguments:
        print(arg)
    print()

    print("**keywords")
    for kw in keywords:
        print(f"{kw}: {keywords[kw]}")
    print()


f(1, 2, 3, a=4, b=5)

Output:

p1:
1

*arguments
2
3

**keywords
a: 4
b: 5

Unpacking Argument Lists

def f(a: int, b: int, c: int, d: int) -> int:
    return a * b * c * d

print(f(2, 3, 4, 5))  # -> 120

my_list_a = [2, 3, 4]
my_list_b = [2, 3, 4, 5]
my_list_c = [3, 4, 5]
print(f(*my_list_a, 5))  # -> 120 (plus a false warning: 'Too many arguments for "f"' from MyPy)
print(f(*my_list_b))  # -> 120
print(f(2, *my_list_c))  # -> 120

my_tuple_c = [3, 4, 5]
print(f(2, *my_tuple_c))  # -> 120

Documentation strings​

def my_function():
    """This is a universal function.
    It does nothing."""
    pass

help(my_function)

Output:

Help on function my_function in module __main__:

my_function()
    This is a universal function.
    It does nothing.

One liner:

def my_function():
    """This is a universal function."""
    pass

Novelization:

def my_function():
    """This is a universal function.
        a
        b
        d
        end"""
    pass

Lambda expressions / anonymous functions​

lambda_expr ::=  "lambda" [parameter_list] ":" expression

Lambda expressions (sometimes called lambda forms) are used to create anonymous functions. The expression lambda parameters: expression yields a function object. The unnamed object behaves like a function object defined with:

def <lambda>(parameters):
    return expression

Example 1:

pairs = [(1, "one"), (2, "two"), (3, "three"), (4, "four")]

def f(x):
    return x[1]

print(pairs) # -> [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]

print((lambda x: x[1])(pairs)) # -> (2, 'two')
print(f(pairs)) # -> (2, 'two')

Example 2:

With a normal function:

pairs = [(1, "one"), (2, "two"), (3, "three"), (4, "four")]

def f(x):
    return x[1]

print(pairs)  # -> [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]

pairs.sort(key=f) 
print(pairs) # -> [(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]
pairs = [(1, "one"), (2, "two"), (3, "three"), (4, "four")]


print(pairs)  # -> [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]

pairs.sort(key=(lambda x: x[1]))
print(pairs) # -> [(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]

Small warning:

print(pairs.sort()) # -> None

because .sort() is an inplace function.

The source code is Open Source and can be found on GitHub.