Functions and methods

Thus far we've covered many primitive python operations, as well as called many functions, its past due to talk about what a function is.

In simple terms: a function is a re-usable piece of code. It is one of the core building blocks of abstraction, allowing complex implementation details to be hidden away behind simple function call syntax.

Syntax.

the def keyword is used to define functions and methods. The minimum syntax is as follows:

def some_function():
  ...  # inside `def some_function()`
# outside def some_function()

Like all control structures, the body of the function is denoted using indentation.

For a function to be re-usable, there are generally bits of data that need to be provided for the function to do anything useful.

Let's start with the geometric function that defines a line, y=mx+b.

"""
uci_bootcamp_2021/examples/functions.py

examples on how to use functions
"""


def y(x, slope, initial_offset):
    """
    This is a docstring. it is a piece of living documentation that is attached to the function.
    Note: different companies have different styles for docstrings, and this one doesn't fit any of them!

    this is just a small example of how to write a function in python, using simple math for demonstration.
    """
    return slope * x + initial_offset

A function is composed of three parts:

  • the function signature contains the def {{name}}({{parameters}}):
  • the function's docstring contains documentation of the function itself, such as what the function does and what is required to use it. Defaults to an empty string if not provided.
  • the function body, which contains the code that is executed when the function is "called"
  • A function may accept zero-or-more parameters.

Calling a function

To use a function, we "call" it.

In earlier chapters we called several functions such as print, here we will clarify the syntax.

To call an object, you refer to it by name and add a pair of parenthesis.

For example,

# tests/test_examples/test_functions.py

# Comparing floats can only be done imprecisely
from math import isclose

# Bring the function defined in the example into scope.
from uci_bootcamp_2021.examples.functions import y
  

def test_y():
    result = y(42, 0.5, 0)

    # Note: cannot compare floats by equality; given floating point inaccuracy.
    # Here we assert that 21.0 is close to the output of the function given known inputs.
    assert isclose(result, 21.0)

When calling a function, you can also specify which parameters the arguments fill. this is known as keyword arguments

For example, using the same function as above:

# tests/test_examples/test_functions.py
...

def test_y_kwargs():
    result = y(x=42, initial_offset=0, slope=0.5)
    # Note: cannot compare floats by equality; given floating point inaccuracy.
    # Here we assert that 21.0 is close to the output of the function given known inputs.
    assert isclose(result, 21.0)

keyword arguments can be presented out of order, but MUST only come after all positional arguments.

  • the caller can also only present one argument for a given parameter

Applying type annotations to function definitions

Type annotations can also be applied to function definitions, to improve readability and documentation.

For the above function, it can also be written as

def y(x: float, slope: float, initial_offset: float = 0) -> float:
    """Same function as above, but this time with type annotations!"""
    return slope * x + initial_offset
  • -> float indicates the value returned by the function is an instance of float.

Something less contrived

Math functions aside, show me a function that isn't better off copy/pasted into the callsite! Sure. Remember that get_valid_input while loop presented in the with for loop chapter? Here is the same piece of code in function form!

# uci_bootcamp_2021/examples/while_loops.py

def get_valid_input(
    prompt: str, valid_minimum: int = 0, valid_maximum: int = 100
) -> int:
    # initialize valid to False.
    valid = False
    result = -1

    # loop while the user hasn't given us a suitable value.
    while not valid:
        # get the user's untrusted input.
        untrusted_input = input(prompt)
        # check if the input is numeric.
        if untrusted_input.isnumeric():
            # if its numeric, we can safely interpret it as an integer
            result = int(untrusted_input)
            # then we can check the bounds
            valid = valid_minimum <= result <= valid_maximum
        if not valid:
            print("invalid input, please try again!")
    return result