Part Six: Functions
Tags: coding, python, programming, tutorial
Published: 2022-12-03 15:27:34

> "Measuring programming progress by lines of code is like measuring aircraft building progress by weight." - _Bill Gates_

---

Welcome to part six. In this series I hope to introduce the basics of coding in Python for absolute beginners in an easy to follow and hopefully fun way. In this article we will look at writing code once but being able to use it as many times as we like without writing it out every time, but first let's take a look at the solution for the challenge set in part five.

---
**Solution to part five's challenge**

---
In this challenge we have a list of integers named `code_list` and we need to loop through this list comparing each list item to the secret code.

To do this we need to use a loop which will iterate through each item and then a nested if / else statement to do the comparing. We could use a while loop as we know we have 1000 codes to loop through but an easier way is to use a for loop:
```
for code in code_list:
    if code == safe_code:
        print("Safe cracked, code was: ")
        print(code)
    else:
        pass
```
We can also print our output on a single line by converting our integer into a string:
```
for code in code_list:
    if code == safe_code:
        print("Safe cracked, code was: " + str(code))
    else:
        pass
```
What is `str()`? Well, I'm glad you asked, it's a method and those are the topic of this article.

---
**Functions**

---

Up to this point I have mentioned functions in passing, but what are they exactly?

I want to introduce you to an important concept in programming which is **D.R.Y.** or **'Don't Repeat Yourself'**. What this means is that we should never be writing the same piece of code over and over in our applications - it looks messy, makes the code more difficult to read, and uses more memory when the application is ran. However, there are many times when we need our code to do something multiple times. Consider the following example which does not adhere to the DRY principle:
```
print("Instructions for fitting wall shelves")
print("Fit 8mm drill bit into drill")
print("Drill 4 x 25mm holes in wall")
print("Insert rawl plugs into holes")
print("open bag of screws")
print("Screw item to wall")

print("Instructions for fitting medicine cabinet")
print("Fit 8mm drill bit into drill")
print("Drill 4 x 25mm holes in wall")
print("Insert rawl plugs into holes")
print("open bag of screws")
print("Screw item to wall")
```
OK, it's a bit of a silly example, but we can see our application needs to use 2 sets of almost identical instructions. To improve on this we can use a function to make most of the instructions repeatable and then simply recall our function each time we need it rather than typing out everything multiple times. Python comes with some built in functions and we have used some of them already. However, we will be writing our own functions from scratch to improve our code.

---

**Creating a Function**

---

First we must define (create) our function. We define a function with the following syntax:


![function syntax](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/llssm8lxta8ft1tgecaz.jpg)

In this case we will name it 'instructions' so as to give an idea of what it will do. Once we have defined our function and given it a name, we then move to the next line down, indent, and then write the code we want to run when the function is called.
```
def instructions():
    print("Fit 8mm drill bit into drill")
    print("Drill 4 x 25mm holes in wall")
    print("Insert rawl plugs into holes")
    print("open bag of screws")
    print("Screw item to wall")
```
Now instead of typing all of the instructions for each task we simply use (call) our function. To call a function, simply write its name followed by brackets '()':
```
print("Instructions for fitting wall shelves")
instructions()

print("Instructions for fitting medicine cabinet")
instructions()
```
Each time we call our `instructions()`, it runs the code we wrote inside the function when we defined it. So now writing this...
```
instructions()
```
...is the equivalent of writing this:
```
print("Fit 8mm drill bit into drill")
print("Drill 4 x 25mm holes in wall")
print("Insert rawl plugs into holes")
print("open bag of screws")
print("Screw item to wall")
```

---
**Giving our functions some input**

---
Using functions allows us to avoid writing the same lines of code over and over which helps us adhere to DRY coding. However, functions can be even more useful when we give them some input so work with.

Consider the following function:
```
def print_name():
    print("Sarah")
```
This function will print the name 'Sarah' any time we call it. This means we don't have to repeat this line in our code, but it also means we can only print 'Sarah' using this function. What if we wanted to print another name? We could make a new function for each name and call them individually when we need them. Or, we can create a function which accepts input known as 'arguments' so that the function knows which name we want it to print.

What is an argument?

By argument, I don't mean shouting at your code (we've all been there though, no judgement). In coding terms, arguments are pieces of input which we feed into a function to allow it to process that input and provide a useful output. Going back to our name printing function, we can modify it to accept an argument which we will call 'name'. We place our arguments in between the brackets when we define our function:
```
def print_name(name):
    print(name)
```
Our name argument is effectively like a variable, a placeholder with a title for whatever we assign to it. The function above will now expect a single input (also known as a 'parameter' - don't get bogged down by the terminology) called 'name' when we call it. Whatever we pass to the function will be stored under 'name' inside the function. In this case the function will print whatever we pass to it as 'name'. Arguments work with any data type.

To pass an argument to a function, we add it between the brackets when calling it. To print a name we could pass a string or the contents of a variable:
```
# Define the function
def print_name(name):
    print(name)

# Call the function
print_name("Floyd")

# Output
'Floyd'

# Using the same function as above

my_name = "Tina"

print_name(my_name)

# Output
'Tina'
```
**Note** - once we have defined a function with an argument, it will expect an argument to be given when we call it. Calling the function with no argument will result in an error but there are some ways around this which I will discuss near the end of this article.

---
**A quick note about scope**

---

> "A good programmer is someone who always looks both ways before crossing a one-way street." - _Doug Linder_

---

Functions can do all manner of things instead of printing names to the screen. That includes creating and modifying variables and data structures. These operate in the same way as normal except that they exist only inside the function when it is called. Consider the following code:
```
def my_func():
    name = "Joe"
    print(name)
```
When this function is called it creates a variable called 'name'. This variable exists only for the time this function is running and the indented code is not accessible outside of the function. For example, if we tried to run the following we would receive an error:
```
def my_func():
    name = "Joe"
    print(name)

print(name)
```
This is because our print statement is trying to access a variable called 'name', however, the error is telling us that there is no variable of that name defined as it exists only inside the function.

This is an example of something called scope and it comes into play a lot in more advanced topics. However, for now all we need to know is that scope is a one way street. We can go with the flow but we cannot go against it. 

Scope is an important subject, particularly as your applications become more complex and ambitious. For more information on scope follow [this link](https://www.askpython.com/python/variable-scopes-in-python).

---
**Passing more than one argument**

---
As you may have guessed, functions can have more than a single argument. In fact, they can have any number of arguments but we must ensure we pass them the same number of inputs when we call them.

Here is an example of a function which takes 2 arguments to join 2 strings - note the arguments are separated by a comma ',':
```
def full_name(first, last):
    print(first + " " + last)
```
Now when we call this function, we must ensure we pass 2 pieces of input:
```
full_name("Bill", "Smith")

# Output
'Bill Smith'
```
Note that the order of the arguments matters here. Passing the arguments in a different order when calling the function would alter the output:
```
full_name("Smith", "Bill")

# Output
'Smith Bill'
```

---
**Time for a practice example**

---
Let's make a simple calculator application using functions to reinforce how they work.

We will define 4 separate function for addition, subtraction, division and multiplication which will each accept 2 numbers as arguments. Each function will then print the result of that operation between the 2 numbers:
```
def add(num1, num2):
    print(num1 + num2)

def sub(num1, num2):
    print(num1 - num2)

def div(num1, num2):
    print(num1 / num2)

def mul(num1, num2):
    print(num1 * num2)
```
Let's test out the addition function by calling it and passing any 2 numbers as arguments:
```
add(43, 65)

# Output
108
```
Make sure your output performs as expected and play around with the other functions to test their outputs too. If you are mathematically inclined you could try creating some more complicated functions.

---
**It's time we talked about recursion**

---

> "To understand recursion, you must first understand recursion" - _old nerd joke_

---
Recursion, recursion, recursion, recursion... where to start? OK, enough bad jokes, recursion is where a function calls itself. Remember when we discussed loops and we looked at infinite loops - a loop with no way to break the cycle which will go on forever. Recursion can be kind of the same thing with functions. Consider the following function:
```
def my_func(count):   
    if count > 0 :       
        print(count)
        my_func(count)

my_func(10)
```
Passing any argument greater than 0 would produce an infinite cycle of function calls as there is no way to break out of the loop and `my_func()` will keep calling itself. 

Just like loops, we need a way to break the cycle:
```
def my_func(count):   
    if count > 0 :       
        print(count)
        my_func(count -1)

my_func(10)
```

Recursion can be used as a tool for accomplishing some tasks, but unless you are confident it is the only way to go and you can create functions capable of  breaking the cycle, it is best avoided.

---
**Arbitrary and Default Arguments**

---
I want to touch briefly on how we address not knowing how many arguments our function will receive or when at least 1 of the function's arguments will be the same every time it is called.

It is always best to plan our functions so that we know what they will receive as arguments each time. However, there may come a time when this is not possible. For example, with our name printing function, some people use middle names and some do not so our function as written above would not be able to deal with that.

To overcome this we use what are known as 'arbitrary arguments' or `*args` for short. When we define our function we tell it we do not know how many arguments to expect by writing a '*' before the argument name. For our name printing function, it would look like this:
```
def print_name(*name):
```
When we use arbitrary arguments, the input given to our function is converted to a tuple (remember those?). We can then use the entire tuple, or slice it to get just the parts we want:
```
# Using the entire tuple

def print_name(*name):
    print(name)

print_name("Billy", "Ray", "Smith")

# Output
('Billy', 'Ray', 'Smith')

# Slicing just what we want - the middle name

def print_name(*name):
    print(name[1])

print_name("Billy", "Ray", "Smith")

# Output
'Ray'
```
In the case where a function will always accept the same argument we can use what are called 'default arguments'. Again going back to our name printing function, perhaps the last name of everyone will be the same. In this case rather than type out the last name as an argument each time we can set it as a default when defining the function:
```
def print_name(first, middle, last="Smith"):
    print(first + " " + middle + " " + last)
```
If we now call our function we only need to pass 2 arguments instead of 3 as the last name is already set:
```
print_name("Billy", "Ray")

# Output
Billy Ray Smith
```
Uh oh! A friend of the family has arrived who has a different last name to everyone else, what do we do? Easy! We can override the default by simply passing a new argument when calling the function:
```
def print_name(first, middle, last="Smith"):
    print(first + " " + middle + " " + last)

print_name("Peggy", "Sue", "Jones")

# Output
Peggy Sue Jones
```

---

**Python's built in functions**

---
As I mentioned at the start of the article, Python comes with some built in functions to handle common tasks for us. We have already used one of them a lot - the print() function. The print function, prints to the screen or terminal whatever we pass it as an argument in the brackets. Now that you know a little about functions, this should make a little more sense. Other built in functions work in a similar way - we call the function name and then pass arguments in the brackets. For an idea of how a built in function works, you can't beat the official Python documentation. Here are some examples of some other built in Python functions:

- bin() - accepts an integer argument and returns it in binary

- float() - accepts an integer or string and converts it to a float

- int() - similar to float() but returns an integer

- len() - returns the length (number of items) of a list or tuple

---
**Returning Values**

---
The final topic for this article is about functions returning values. So far we have been getting our functions to print their results to the screen as this is the simplest way to show they are working as expected. However, what if we wanted to actually use the output of a function for something else? Maybe we want to store the result in a variable to use later, or perhaps use it as the input for another function? To do this we use a new key word `return`. Returning a value from a function changes its scope and makes it available to use outside of the function.

We will stick to the name printing function for simplicity but now instead of printing a name we will return it:
```
def print_name(name):
    return name
```
Now we call the function as before:
```
print_name("John")
```
Nothing happened right? What is happening behind the scenes is that the function ran and sent back the value of `name` but nobody was listening - we had not written any code to do anything with it. Let's store that returned value in a variable so we can see it and use it. To do that we assign the value of a variable to our function call:
```
my_name = print_name("John")
```
Now whatever is returned by calling this function is stored in our variable. You can verify it has worked by printing your variable if you like.

We can return multiple values from a function and store them in multiple variables. Values will be assigned to the variables in the same order they are returned:
```
def print_name(first, last):
    return first, last

first_name, last_name = print_name("John", "Smith")
```

OK, let's take it up a notch. We will define one function which will get a name from a user, and another which will take the returned value and print the name to the screen. First we define our 2 functions:
```
def get_name():
    name = input("What is your name? ")
    return name

def print_name(name):
    print(name)
```
Now we simply need to call the print function to make it all work. We can now store the value of `get_name` in a variable and then pass it on to `print_name` as an argument:
```
my_name = get_name()

print_name(my_name)
```
We can now get any name from a user and then print whatever that name happens to be. We can also shorten this code even further by removing `my_name` all together. Remember that calling a function will run that code at that point. Well, that includes inside the argument brackets of a function call! We can achieve the same result as before by instead using:
```
print_name(get_name())
```
When print name is called it looks for an argument to be passed. Instead of a variable, there is a function call instead. It will run that function there and then and whatever result is returned that is what it will use as the argument.

---
**Challenge**

---
For this challenge, all I want you to do is use what you have learned about flow control, loops and functions to improve on the calculator application we made earlier. In its current state, the calculator is pre-written and uses only arguments we have written into the code. It also completes only one calculation before exiting.

To improve the calculator application have a go at the following tasks:

1. Take input from the user about what kind of calculation they would like to do
2. Ask for 2 numbers from the user and then pass these as arguments to the relevant function. Hint - you may need to convert the data you receive to something more usable using a built in Python function.
3. Display the results and then ask the user if they would like to perform another calculation
4. If the user answers 'yes', loop back to the start of the application and run it again. If they answer 'no', exit the application.

By now I hope that you are starting to get a better feel for the concepts that we have covered so far and that you are using other resources online to improve your learning. As such I will not be walking you through this challenge's answer step by step but rather I will simply post 1 possible solution you could use. Remember, there is nearly always multiple ways of accomplishing your goal in coding. Good luck, I know you can do it!

---
**Conclusion**

---
We have now covered 3 important concepts in programming - flow control, loops, and functions. By using these building blocks you can now write some pretty sophisticated applications. There is another level though, something which many modern programmers aspire to do efficiently and elegantly - Object Orientated Programming (OOP).

I will be getting into the basics of OOP in a future article. However, before that I want to discuss some other topics which I hope will help you learn and overcome some difficulties. The next article will be discussing the subject of error handling. So, please keep your solution to the calculator challenge as we will be using it to demonstrate error handling next time.

Once again, thank you for reading, constructive feedback is always appreciated and I look forward to seeing you next time!

Simon.