Functions

Functions let us summarize frequently used sequences of statements in one place. This removes redundancies in our code, which is in line with the DRY principle (Don't Repeat Yourself). Functions also allow us to abstract from more complex tasks, helping us to better organize both our code and the process of its development. Thus, we can define a function (as a sequence of statements), which we can then call when needed in our program. Functions that we call on objects (object.some_function(arguments) instead of just some_function(arguments), cf. OOP) are usually referred to as methods.

The definition of a classic function or method has a head and a body. The head consists of (1) the function or method name and (2) the (abstract) parameters, which may be annotated with a type and for which we may specify a default value (parameter:type=default). In a function call, parameters are replaced by matching values, which are called (concrete) arguments. Parameters with default values may be omitted (in this case, the default values are used as the arguments).

An even more flexible number of parameters can be achieved using * and ** in the function definition. A parameter annotated with * receives a tuple of arguments; a parameter annotated with ** receives a dict; a parameter with * must be specified before a parameter with **. You can find out more here.

When a function is called, the arguments are passed to the function, i.e., the values of the arguments are available in the function body under the name of the arguments for which they were passed. This way, the function receives most (ideally: all) information needed to execute the sequence of statements contained in its body. When the statements in the function body are executed, the variables mentioned are looked up from the inside outwards. This means that if a variable is not found in the function itself, it is sought in the enclosing scope, and if a variable outside the function has the same name as a variable inside the function, the outer variable is shadowed by the inner variable.

In principle, everything that happens inside a function stays inside the function. The function interacts with its environment only via its return statement. Return statements consist of the keyword return followed by one or more return values, which are passed from the function to its enclosing scope. If we omit the return statement, None is returned automatically. Multiple return values are returned as a tuple.

Tuples as return values can be unpacked (i.e., split into the individual values) using *. Unpacking is available not only for tuples (and also using ** instead of *), read more here).

With return statements, function calls influence their environment explicitly. Implicit influences, the so-called side effects, are also possible but they are usually undesirable since they tend to make programs harder to understand and debug.

If we feel the exceptional (!!!) need to directly change values outside a function, we can do so using the keyword global (or, in some cases of nested functions, nonlocal). Read more here.

# Simple function definitions

def say_hello():             # Head without parameters
    """Greets the world."""  # Docstring providing information on what the function does
    print('Hello, World!')   # Prints 'Hello, World!' as a side effect

def sum_two(one:int, two=2): # Head with two parameters, one with a type annotation
                             # and one with a default value
    result = one + two
    return result, one, two  # Returns a tuple, short for (result, one, two)

# Simple function calls
say_hello()     # Prints 'Hello, World!' to the console

sum_two(1,3)    # Returns (4,1,3)

sum_two(1)      # Returns (3,1,2) (omitted argument has default value of 2)

Comprehensions and Generators

Comprehensions and generators combine the previously introduced control structures with ideas from functional programming to help us say more with less code. The most popular construct is the list comprehension, which mimicks the set builder notation used in mathematics and is easily extended to sets and dicts (set comprehension and dict comprehension):

[x for x in 'abracadabra' if x not in 'aeiou']  # ['b', 'r', 'c', 'd', 'b', 'r']

Here, we only note that you can read more about these constructs here, here, and here, and that they can be used to specify a memory-friendly solution to the 'Small Gauß' in one line:

sum(x for x in range(101))    # 5050 - sum of the numbers 1 to 100

results matching ""

    No results matching ""