How did it go in yesterday’s challenge? What we’ll learn today will make a lot of sense in the context of what you’ve been working on.

This post is part of #100DaysOfPython, check out yesterday's post here if you haven't already. Or go to the index of all 100 days.

Here’s a potential solution to the challenge:

``````students = [
{ "name": "Jose", "marks": [56, 77, 97] },
{ "name": "Rolf", "marks": [45, 80] },
{ "name": "Anna", "marks": [87, 75, 100, 95, 98] },
{ "name": "Mary", "marks": [67, 70, 80] },
{ "name": "Stuart", "marks": [44, 55] },
{ "name": "Sophie", "marks": [86, 90, 100, 100] },
]

total = 0
count = 0

for student in students:
total += sum(student['marks'])
count += len(student['marks'])

print(total / count)
``````

If you run that code, it should return something like `79.05263157894737`.

## Functions

Functions in Python are names for blocks of code. You can define a few lines of code, give them a name, and then whenever you invoke that name the lines of code will run.

calling a function is the same as invoking it or running it.

For example:

``````def my_function():
print("Hello")
``````

If we type that into a file and run it, we won’t see anything. It’ll just terminate without printing `”Hello”`. That’s because defining the function doesn’t run it. We must call it.

``````def my_function():
print("Hello")

my_function()
``````

We’ve now added the function call, which is just the name of the function and the pair of brackets—but not the `def` keyword or the colon, `:`.

As soon as we call the function, Python jumps to the first line inside the function (which is below the colon and indented). When the function ends, it continues on the next line after the original function call.

### What are the brackets for?

In any function, the brackets are to enclose any values we want to give the function. Any function can take parameters if it is defined to do so. For example, the following two functions:

``````def add_two(x, y):
print(x + y)

def hi():
print("Hello")
``````

The `add_two` function has two parameters called `x` and `y`. That means that inside the function we can use `x` and `y` as if they were variables defined inside the function.

But what are the values of `x` and `y`?

We give values to the function as arguments when we call the function. For example:

``````add_two(13, 45)  # prints 58
``````

In this example, `x = 13` and `y = 45`.

Notice that if you give arguments to a function that accepts no parameters, you’ll get an error!

``````hi(45)  # TypeError: hi() takes 0 positional arguments but 1 was given
``````

The same problem happens if you give the wrong number of arguments to a function, even if it does accept some arguments.

Arguments vs Parameters?

Parameters are the “variables” defined inside the function as values it will accept. Arguments are the values that we give it when we call it.

You could say that for each parameter, its value is that of the corresponding argument.

### The concept of scope

Scope in Python is very important—you'll be learning a lot about it over the next 89 days.

A function has scope, and that means there are variables which can only be accessed within the function, and not outside. Any variable defined inside a function will cease to exist once the function has finished running.

For example, let's take our `add_two()` function:

``````def add_two(x, y):
print(x + y)

print(x)
``````

What do you think we would see output to the console?

The first thing we do is define a function which prints the sum of its parameters. Defining the function doesn't call it, so we would see nothing until line 3 runs.

`add_two(43, 21)` calls the function, and we'd see `64` printed out.

`print(x)` tries to access the variable `x`, but because the variable is created inside the function, it is no longer available outside the function.

We would get a `NameError`, with an accompanying message: `NameError: name 'x' is not defined`. The variable we tried to access didn't exist when we tried to access it; it had already been destroyed by Python in order to be more efficient.

## Functions returning values

Functions are not limited to printing their output. They can `return` values so that the caller can use it. Here’s an example:

``````def add_two(x, y):
return x + y

print(x + y)

n1 = add_two(13, 45)
n2 = p_add_two(13, 45)
``````

What do you think the values of `n1` and `n2` would be?

Because `add_two` returned a value, `n1` equals `58`.

Because `p_add_two` did not return a value, `n2` equals `None`. All functions in Python return `None` if nothing else is returned. It’s a special value in Python that means “nothing”.

Notice that `p_add_two` did print something out to the console, whereas `add_two` did not. We would see something only when calling `p_add_two`. We wouldn’t see anything when we call `add_two`, but we would then have access to the returned value!

## Functions in yesterday’s challenge

At the start of this post I mentioned that functions would make a lot of sense in the context of yesterday’s challenge. Let’s take a look at the solution I proposed at the start of the post:

``````students = [
{ "name": "Jose", "marks": [56, 77, 97] },
{ "name": "Rolf", "marks": [45, 80] },
{ "name": "Anna", "marks": [87, 75, 100, 95, 98] },
{ "name": "Mary", "marks": [67, 70, 80] },
{ "name": "Stuart", "marks": [44, 55] },
{ "name": "Sophie", "marks": [86, 90, 100, 100] },
]

total = 0
count = 0

for student in students:
total += sum(student['marks'])
count += len(student['marks'])

print(total / count)
``````

We could make it reusable it with a function:

``````def average(collection, key):
total = 0
count = 0

for element in collection:
total += sum(element[key])
count += len(element[key])

``````

This function takes has two parameters: `collection` and `key`.

It:

1. Creates two variables to store a `total` sum of all grades and a total `count` of all grades.
2. Then it loops over the `collection` variable, which could be any list or tuple.
3. Then it tries to access the `key` key of each element in the collection. If `key == 'marks'`, then it will try to access the `'marks'` key of each element.
4. It adds the `sum` and `len` to the respective variables
5. Outside the loop it returns the average, which is `total / count`.

We could then use this function like so:

``````students = [
{ "name": "Jose", "marks": [56, 77, 97] },
{ "name": "Rolf", "marks": [45, 80] },
{ "name": "Anna", "marks": [87, 75, 100, 95, 98] },
{ "name": "Mary", "marks": [67, 70, 80] },
{ "name": "Stuart", "marks": [44, 55] },
{ "name": "Sophie", "marks": [86, 90, 100, 100] },
]

def average(collection, key):
total = 0
count = 0

for element in collection:
total += sum(element[key])
count += len(element[key])