"We could - in principle - write code by adding more and more statements after one another. This might well work and do what we want, but would have quite some disadvantages.\n",
"\n",
"* The code becomes very long very quickly (\"Spaghetti - Code\")\n",
"* We would need to keep track of *a lot* of variables as each variable is valid all the time.\n",
"* The only way to re-use code is to copy/paste it.\n",
"\n",
"\n",
"It works... but it is neither nice, efficient, readable or maintainable.\n",
"\n",
"Functions help us to structure code. Essentially, they assign a block of code to a call of this function.\n",
"The general structure is:\n",
"\n",
"```\n",
"def my_func(<arguments>):\n",
" ....\n",
" my code goes here\n",
" ....\n",
"\n",
" return <return value>\n",
"```\n",
"\n",
"We notice the following:\n",
"* The function is defined using the keyword ```def```\n",
"* The name of the function is given by ```my_func```\n",
"* We can pass ```<arguments>``` arguments to the function, This is optional.\n",
"* Functions can have one or multiple return value(s), indicated by the keyword ```return```. This is optional.\n",
"\n",
"Some programming languages distinguish between functions that return a value (\"functions\") or not (\"procedures\"). Python does not and we call them all \"functions\".\n",
"\n",
"Therefore, the simples function is something like the following:\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"This is a small function that says Hello.\n"
]
}
],
"source": [
"# define the function\n",
"def say_hello():\n",
" print(\"This is a small function that says Hello.\")\n",
"\n",
"# call the function \n",
"say_hello()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This is probably not the most useful function we can think of - but it allows us to see what we can do:\n",
"\n",
"* We can assign a descriptive name to a block of code. The name should give a short indication about what the function does so that when reading the code we can follow what happens\n",
"* We can re-use the code. Whenever we want to execute the same block of code, we call the function. This makes it much easier to write understandable and efficient code - and also helps us to maintain the code. Whenever we find a bug, we need to fix it only in this function.\n",
"\n",
"***Best practice***\n",
"\n",
"Functions should only done one thing. This makes them as short, concise and efficient as possible. It also makes them testable (we know what to expect as output), and more maintainable.\n",
"\n",
"While there is an overhead in calling a function, it is *very* small compared to writing inefficient and lengthy code. Do not hesitate to put even a single line of code into a separate function if you think that this will help to make the code easier to read, debug and maintain.\n",
"\n",
"## Function arguments\n",
"\n",
"Functions can have one or more arguments that we pass when calling the function."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The sum is: 7.1\n"
]
}
],
"source": [
"def my_sum(x, y):\n",
" return x + y\n",
"\n",
"sum = my_sum(3,4)\n",
"print('The sum is: {}'.format(sum))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can make use of the arguments in several ways: Above we pass two numbers and naturally assume that $x=3, y=4$ when we compute the sum. Here, the order of the variables and the arguments matter. The variables are assigned in the order we pass them.\n",
"\n",
"We can also specify this explicity."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"x=3, y=4\n",
"The sum is: 7\n"
]
}
],
"source": [
"def my_sum(x, y):\n",
" print('x={}, y={}'.format(x,y))\n",
" return x + y\n",
"\n",
"sum = my_sum(y= 4, x=3)\n",
"print('The sum is: {}'.format(sum))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here, we specify the variables directly and can choose any order.\n",
"\n",
"We can also define default arguments, i.e. a value, that is taken when we do not specify the variable."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"x=3, y=4\n",
"The sum is: 7\n",
"x=1, y=4\n",
"The sum is: 5\n",
"x=2, y=6\n",
"The sum is: 8\n",
"x=2, y=4\n",
"The sum is: 6\n",
"x=2, y=1\n",
"The sum is: 3\n"
]
}
],
"source": [
"def my_sum(x=3, y=4):\n",
" print('x={}, y={}'.format(x,y))\n",
" return x + y\n",
"\n",
"sum = my_sum()\n",
"print('The sum is: {}'.format(sum))\n",
"\n",
"sum = my_sum(1)\n",
"print('The sum is: {}'.format(sum))\n",
"\n",
"sum = my_sum(2,6)\n",
"print('The sum is: {}'.format(sum))\n",
"\n",
"sum = my_sum(x=2)\n",
"print('The sum is: {}'.format(sum))\n",
"\n",
"sum = my_sum(x=2, y=1)\n",
"print('The sum is: {}'.format(sum))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"There are a (very) few use-cases where you do not know how many arguments a user will pass. We can do this by preceeding the variable name in the argument list with a single ```*```.\n",
"\n",
"***Note***\n",
"\n",
"These arguments are often abbreviated as ```*args```, as we do not know what they will be."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"dog\n",
"dog cat\n",
"dog cat hamster\n"
]
}
],
"source": [
"def print_pets(*args):\n",
" print(args)\n",
"\n",
"print('dog')\n",
"\n",
"print('dog', 'cat')\n",
"\n",
"print('dog', 'cat', 'hamster')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Sometimes, we might want to use a function with an arbitrary number of arguments and we also do not know what kind of information is going to be passed. We can use the ```**``` construct to accept as many key-value pairs as are provided.\n",
"As we can see, the ```**profile``` creates a dictionary into which we (implicitly) add all the key-value pairs we pass as arguments to the function.\n",
"Inside the function, we can access and change the dictionary as we normally would (e.g. add a new field with key ```name```).\n",
"\n",
"As we can also see, this makes the code less readable and - because we do not know what the user will pass to the function - we also would not know how to check whether this was intentional, etc.\n",
"\n",
"One good use-case for such a construct is that we want to use a generic way to pass options to our code, such as keyword arguments.\n",
"In general, when we find ourselves in the situation to need such constructs with ```*``` or ```**```, we should take the opportunity to reflect if we really need this and if we can use simpler approaches to make our code more readable and maintainable.\n",
"\n",
"## Local variables"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The value of i is 0\n"
]
}
],
"source": [
"def my_function(x, y):\n",
" i = 0\n",
" print('The value of i is {}'.format(i))\n",
" return x + y\n",
"\n",
"sum = my_function(3,4)\n",
"\n",
"# uncommenting this wil fail.\n",
"# print('The value of i is {}'.format(i))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"So far, all variables we have used in our code, inside or outside loops are always available: Once we have used a variable, it is defined and the Python interpreter keeps it in memory. This is our ***global*** scope.\n",
"\n",
"As our example above shows, variables that we use inside a function are only valid within this function, a ***local*** scope.\n",
"\n",
"This has the benefit that we can define the variables we need inside the function and we do not need to clean up afterwards. Again, this makes the code more readable and maintainable, as we can move blocks of code *together with the relevant variables* into a function.\n",
"\n",
"***Best practice***\n",
"\n",
"When defining a function, do not rely on global variables defined outside the scope of this function. Instead, all variables that you need inside the function should be passed as arguments, all variables that you need back, should be part of the ```return``` statement.\n",
"\n",
"\n",
"The following works but is clearly not a good idea:"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The sum is 10\n"
]
}
],
"source": [
"sum = 0\n",
"x = 3\n",
"y = 7\n",
"\n",
"def stupid_sum():\n",
" global sum\n",
" sum = x + y\n",
"\n",
"stupid_sum()\n",
"print('The sum is {}'.format(sum))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here, we use the keyword ```global``` to force Python to make use of the variable ```sum``` defined outside the function such that it modifies this variable (otherwise, a local variable would be created with the same name, but not returned. ---try it by commenting out line with ```global sum```).\n",
"Obviously, the keyword exists because there may be some legitimate use-case for it.\n",
"\n",
"Generally, avoid such a pattern and have a clearly defined list of input and output values. Otherwise you loose all benefits to writing a function in the place."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Exercise\n",
"\n",
"Rewrite the Fibonacci series as a function. The input value should be the number of Fibonacci numbers generated, the output should be a list containing the Fibonacci numbers.\n",
"\n",
"Call your function and have it return 10 Fibonacci numbers.\n",
"\n",
"The output should be like: ```The Fibonacci numbers are: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]```"
We could - in principle - write code by adding more and more statements after one another. This might well work and do what we want, but would have quite some disadvantages.
* The code becomes very long very quickly ("Spaghetti - Code")
* We would need to keep track of *a lot* of variables as each variable is valid all the time.
* The only way to re-use code is to copy/paste it.
It works... but it is neither nice, efficient, readable or maintainable.
Functions help us to structure code. Essentially, they assign a block of code to a call of this function.
The general structure is:
```
def my_func(<arguments>):
....
my code goes here
....
return <return value>
```
We notice the following:
* The function is defined using the keyword ```def```
* The name of the function is given by ```my_func```
* We can pass ```<arguments>``` arguments to the function, This is optional.
* Functions can have one or multiple return value(s), indicated by the keyword ```return```. This is optional.
Some programming languages distinguish between functions that return a value ("functions") or not ("procedures"). Python does not and we call them all "functions".
Therefore, the simples function is something like the following:
%% Cell type:code id: tags:
``` python
# define the function
defsay_hello():
print("This is a small function that says Hello.")
# call the function
say_hello()
```
%% Output
This is a small function that says Hello.
%% Cell type:markdown id: tags:
This is probably not the most useful function we can think of - but it allows us to see what we can do:
* We can assign a descriptive name to a block of code. The name should give a short indication about what the function does so that when reading the code we can follow what happens
* We can re-use the code. Whenever we want to execute the same block of code, we call the function. This makes it much easier to write understandable and efficient code - and also helps us to maintain the code. Whenever we find a bug, we need to fix it only in this function.
***Best practice***
Functions should only done one thing. This makes them as short, concise and efficient as possible. It also makes them testable (we know what to expect as output), and more maintainable.
While there is an overhead in calling a function, it is *very* small compared to writing inefficient and lengthy code. Do not hesitate to put even a single line of code into a separate function if you think that this will help to make the code easier to read, debug and maintain.
## Function arguments
Functions can have one or more arguments that we pass when calling the function.
%% Cell type:code id: tags:
``` python
defmy_sum(x,y):
returnx+y
sum=my_sum(3,4)
print('The sum is: {}'.format(sum))
```
%% Output
The sum is: 7.1
%% Cell type:markdown id: tags:
We can make use of the arguments in several ways: Above we pass two numbers and naturally assume that $x=3, y=4$ when we compute the sum. Here, the order of the variables and the arguments matter. The variables are assigned in the order we pass them.
We can also specify this explicity.
%% Cell type:code id: tags:
``` python
defmy_sum(x,y):
print('x={}, y={}'.format(x,y))
returnx+y
sum=my_sum(y=4,x=3)
print('The sum is: {}'.format(sum))
```
%% Output
x=3, y=4
The sum is: 7
%% Cell type:markdown id: tags:
Here, we specify the variables directly and can choose any order.
We can also define default arguments, i.e. a value, that is taken when we do not specify the variable.
%% Cell type:code id: tags:
``` python
defmy_sum(x=3,y=4):
print('x={}, y={}'.format(x,y))
returnx+y
sum=my_sum()
print('The sum is: {}'.format(sum))
sum=my_sum(1)
print('The sum is: {}'.format(sum))
sum=my_sum(2,6)
print('The sum is: {}'.format(sum))
sum=my_sum(x=2)
print('The sum is: {}'.format(sum))
sum=my_sum(x=2,y=1)
print('The sum is: {}'.format(sum))
```
%% Output
x=3, y=4
The sum is: 7
x=1, y=4
The sum is: 5
x=2, y=6
The sum is: 8
x=2, y=4
The sum is: 6
x=2, y=1
The sum is: 3
%% Cell type:markdown id: tags:
There are a (very) few use-cases where you do not know how many arguments a user will pass. We can do this by preceeding the variable name in the argument list with a single ```*```.
***Note***
These arguments are often abbreviated as ```*args```, as we do not know what they will be.
%% Cell type:code id: tags:
``` python
defprint_pets(*args):
print(args)
print('dog')
print('dog','cat')
print('dog','cat','hamster')
```
%% Output
dog
dog cat
dog cat hamster
%% Cell type:markdown id: tags:
Sometimes, we might want to use a function with an arbitrary number of arguments and we also do not know what kind of information is going to be passed. We can use the ```**``` construct to accept as many key-value pairs as are provided.
As we can see, the ```**profile``` creates a dictionary into which we (implicitly) add all the key-value pairs we pass as arguments to the function.
Inside the function, we can access and change the dictionary as we normally would (e.g. add a new field with key ```name```).
As we can also see, this makes the code less readable and - because we do not know what the user will pass to the function - we also would not know how to check whether this was intentional, etc.
One good use-case for such a construct is that we want to use a generic way to pass options to our code, such as keyword arguments.
In general, when we find ourselves in the situation to need such constructs with ```*``` or ```**```, we should take the opportunity to reflect if we really need this and if we can use simpler approaches to make our code more readable and maintainable.
## Local variables
%% Cell type:code id: tags:
``` python
defmy_function(x,y):
i=0
print('The value of i is {}'.format(i))
returnx+y
sum=my_function(3,4)
# uncommenting this wil fail.
# print('The value of i is {}'.format(i))
```
%% Output
The value of i is 0
%% Cell type:markdown id: tags:
So far, all variables we have used in our code, inside or outside loops are always available: Once we have used a variable, it is defined and the Python interpreter keeps it in memory. This is our ***global*** scope.
As our example above shows, variables that we use inside a function are only valid within this function, a ***local*** scope.
This has the benefit that we can define the variables we need inside the function and we do not need to clean up afterwards. Again, this makes the code more readable and maintainable, as we can move blocks of code *together with the relevant variables* into a function.
***Best practice***
When defining a function, do not rely on global variables defined outside the scope of this function. Instead, all variables that you need inside the function should be passed as arguments, all variables that you need back, should be part of the ```return``` statement.
The following works but is clearly not a good idea:
%% Cell type:code id: tags:
``` python
sum=0
x=3
y=7
defstupid_sum():
globalsum
sum=x+y
stupid_sum()
print('The sum is {}'.format(sum))
```
%% Output
The sum is 10
%% Cell type:markdown id: tags:
Here, we use the keyword ```global``` to force Python to make use of the variable ```sum``` defined outside the function such that it modifies this variable (otherwise, a local variable would be created with the same name, but not returned. ---try it by commenting out line with ```global sum```).
Obviously, the keyword exists because there may be some legitimate use-case for it.
Generally, avoid such a pattern and have a clearly defined list of input and output values. Otherwise you loose all benefits to writing a function in the place.
%% Cell type:markdown id: tags:
### Exercise
Rewrite the Fibonacci series as a function. The input value should be the number of Fibonacci numbers generated, the output should be a list containing the Fibonacci numbers.
Call your function and have it return 10 Fibonacci numbers.
The output should be like: ```The Fibonacci numbers are: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]```