Xtralien Scientific Python: Functions

 

Functions are a way of grouping actions and processes, whilst additionally providing a reusable interface. Although code needs to be designed to take advantage of this reusability, it is often useful to do so to reduce total development time.

Functions also provide a standardised interface to hardware and other software. This allows you to change the underlying mechanics of the function without having to change the interface that you use to access it.

Functions can also be used in conjunction with classes, which are user-defined types. When used in this way functions are referred to as methods.

A useful anaolgy is that well-designed functions often act as black boxes. This is because they should provide a consistent interface, with anything involved concealed within the function.

Defining a function

When creating a function you are defining what it does. This is why when you define a function you use the def keyword.

def function():
    pass

The definition of the function function uses a new keyword pass, which tells Python to do nothing. This is useful when starting to structure your function definitions. When indenting you can use pass to keep your code correct when you don’t know what the final definition will be. It is a useful placeholder.

The example above does one thing only. It defines a function stored in the variable function. This function takes to arguments and returns no value.

One example of when you may want a function that has no input or output is when the function does a very specific job.


def print_header():
    print("HEADER")

The above function has limited functionality but shows how a function contains instructions on further actions to take. Unfortunately these functions take no arguments and so have limited capability. The next sections alleviate this problem.

Returning from functions

Most of the time when using functions you will want to return a value from a function. In Python you can only return a single value from a function, although this can be a tuple, allowing you to return as many values as you wish.

To return values from functions in Python you use the return keyword. When used, this will stop the function where it is and return the specified value.

Note: you do not need to specify a return value, allowing return to be used to exit a function.

def get_random_percentage():
    return random() * 100

In Python the random function is from the random module, and provides a number betwwen 0 and 1 as a result. The example above will simply take this value and multiply it by 100 to get a value between 0 and 100.

It is also possible to use arguments to create a function that acts on the input and returns a result based on the inputs.

Taking arguments

In Python you can choose to take arguments in your function by naming the arguments that you want.

def miles_to_km(distance):
    return distance * 1.61

miles = 2 
km = miles_to_km(miles)
print("There are {} km in {} miles".format(km, miles))

In the example above the variable distance is ‘bound’ to the argument passed. In the case above the value 2 is bound, meaning that for the duration of the function, the variable distance will contain the value 2. The miles_to_km function will then multiply the provided distance by 1.61 and return the result. This example will print There are 3.22 km in 2 miles to the console.

Default arguments

It is possible to provide default arguments to functions so that not all of the arguments need to be provided every time that the function is used.

One example of a function that uses default arguments is the range function. This function will has default values for start and step of 0 and 1 respectively. It is possible to override this simply by supplying the other arguments.

start = 0
end = 10
step = 1

range(start, end, step)

range(end)

Because the arguments are the same as the defaults in this case the result will be an iterator containing the integers from 0 to 9 inclusive.

To define your own function with default arguments you can use syntax like below.

def print_name(name="John"):
    print("Hello", name)
    
print_name()
print_name("Jenny")

The above example shows how optional arguments can be used to provide alternatives for when an argument is not provided. In this case, when the function is called the first time, without any arguments the string Hello John will be printing. This is because the variable name defaults to the string John. In the second call of the function the name "Jenny" is provided. This will override the value for name, meaning that Hello Jenny will be printed to the console.

Scopes

Scoping in Python refers to variables. There are two scope ‘levels’, global and local. By default you can only read the local scope. The global level is where most of your functions exist. The local scope usually only exists when you are inside a function, and is unique to a specific instance of the function execution.

When you are outside any function the global scope is the same as the local scope, however when you enter a function then a new local scope is created. Any assignments in this local scope are not applied to the global scope, and are lost when the function ends. An example of this is below.

# In global scope
x = 1


def set_x():
    # Created a new local scope
    x = 2
    print("function x:", x)
    # Destroy local scope

 
print("x:", x)
set_x()
print("x:", x)

The above example shows how scopes are created and destroyed as you enter and leave functions. Both of the print statements around the set_x function call will print x: 1. This is because that is the value of x in the global scope. The local scope makes it’s own x and assigns that value to 2. However, as the function finishes the local scope gets destroyed.

When access to the global scope is required from inside a function it is possible to declare that you require access to that variable. To do this you simply need to declare that you want to use the variable before you attempt to use it. Rewriting the example above we can see how access to a variable in the global scope can be achieved.


# In global scope
x = 1


def set_x():
    # Specify that we are using the global x
    global x
    # Created a new local scope
    x = 2
    print("function x:", x)
    # Destroy local scope

 
print("x:", x)
set_x()
print("x:", x)

This new example will modify the global variable x and replace the value with the value 2. In this case the last print statement will show that the value of x has changed from 1, as initially set, to 2.