Defining the function
Now that we have reviewed built-in functions, you probably have some understanding of how to use them as a consumer. But how can a custom function be created? It's very simple! You just have to observe the following structure:
def function_name(arguments):
‘''Documentation string'''
# code inside, using arguments
return result
Let's break this down. Here, def is a reserved keyword that tells Python that we are going to create a function. After that comes the name of the function—following the same rules as variables. The name is followed by a parenthesis. If the function requires any argument we should state them here by name (you can think of them as new variables). If you don't anticipate any arguments, a parenthesis should be kept empty.
The colon after the parenthesis marks the start of the function's inner scope. Note that when you hit Enter, both Jupyter and VS Code adds an indentation of four white spaces on the next line – stick with it! This indentation is important; in contrast to other languages, in Python, it is part of the syntax and directly impacts the computation. Everything within the same indentation stays in the same scope, for example, in our case – a function's internal level. Variables assigned here, including function arguments, will be dropped once we get out of this indentation.
On the first line of the function's internal scope, we usually define docstring – a small note on what functions achieve and how to use it. The docstring is not required, but we really, really recommend that you write them up; you will thank yourself later.
Finally, we can write the actual code of the function, also preserving the indentation. Inside this code, we can use the function's arguments as variables, even though there is no value in them yet.
To exit the function and return a value, use the return keyword – it will return whatever variable is stated after. Once the function executes return, Python exits the function, and no code after the return statement will be executed, ever. Of course, functions do not have to return anything. For example, a function that creates a new folder on a filesystem won't need to return any result within Python.
Here is an example. Imagine we want to compute a negative value v to the power of p. Here is how we should define our function:
def negative_power(v, p):
'''Return negative value v in to the power of p'''
return -1 * (v**p)
Here, we first declare the function with the def keyword. Next, we state the name of the function—negative_power. Then, we declare two required arguments—v and p. On the next line, we define a docstring, explaining the meaning of this function. Lastly, we define the actual code that that function runs—in this case, it will return a negative value of v to the power of p.
Let's try this function out. The value of 2 to the power of 3, multiplied by -1, is -8. This seems correct, and the same for 3 to the power of 2—the outcome is -9:
>>> negative_power(2, 3)
-8
>>>negative_power(3, 2)
-9
So far, Python has assigned v and p by their order. We can also specify them explicitly, by assigning them as arguments. This way, the order does not matter. The following is an illustration of that – we assigned the arguments in reverse order, and everything works fine:
>>> negative_power(p=2, v=3)
-9
The named (explicit) assignment is arguably more desirable as the code is then easier to read and more prone to human errors.
In many cases, functions require a lot of arguments, many of which could be optional and tedious to restate on every function call. To avoid that, there is a way to state the argument's default value. In the next section, we will cover how to do that.