# Functions

Course: [Python](https://www.devtutorials.ai/learn/python) · Section: Control Flow
Source: https://www.devtutorials.ai/learn/python/control-flow/functions

> Writing and calling functions, every kind of argument, variable scope and the LEGB rule, packing and unpacking, annotations and docstrings.

Think about a kettle.

You fill it, switch it on, and it boils the water. You do not need to know how the element works, how the thermostat decides it is done, or what the wiring looks like. You just use it, and you use it again tomorrow.

Somebody built that once. Everybody uses it many times.

A **function** is the same idea in a program. You write a piece of work once, give it a name, and use it whenever you need it.

```python runnable title="Write it once, use it three times"
def grade_for(marks):
    if marks >= 80:
        return "A"
    if marks >= 60:
        return "B"
    return "C"

print(grade_for(90))
print(grade_for(78))
print(grade_for(45))
```

The grading rule is written once. It was used three times, and if the rule changes there is exactly one place to change it.

## What You Will Learn

In this lesson, you will learn:

- How to define a function, call it, and get an answer back
- The different ways to pass values in, including `*args` and `**kwargs`
- Where a variable can be seen, and the rule Python uses to find it
- How packing and unpacking move values in and out of functions
- The functions Python already gives you
- How to label what a function expects and returns
- How to describe a function for the next person who reads it

A function is a named piece of work you can run whenever you need it.

## Introduction to Functions

### What Is a Function?

A **function** is a block of code with a name. The code does not run when you write it. It runs when you **call** it by name.

**Calling** a function means running it. You call it by writing its name followed by brackets, like `grade_for(78)`. The brackets tell Python to run the body now.

### Why Functions Are Used

Without functions, repeated work gets copied.

```python runnable title="The same rule, written three times"
maths = 90
science = 78
english = 45

if maths >= 80:
    print("Maths: A")
elif maths >= 60:
    print("Maths: B")
else:
    print("Maths: C")

if science >= 80:
    print("Science: A")
elif science >= 60:
    print("Science: B")
else:
    print("Science: C")

if english >= 80:
    print("English: A")
elif english >= 60:
    print("English: B")
else:
    print("English: C")
```

Three copies of one rule. If the A boundary moves to 85, you have to change it in three places and remember all of them.

### Benefits of Functions

- **One place to change.** The rule lives in one block.
- **A name that explains.** `grade_for(marks)` says what it does; fifteen lines of `if` do not.
- **Easier to test.** You can try one function on its own.
- **Less to read.** The detail is tucked away until you need it.

### Function Syntax

```text
def name(parameters):
    body
    return value
```

- `def` tells Python a function is being defined
- the name follows the same rules as a variable name
- the brackets hold the **parameters**, which may be empty
- the line ends in a colon, and the body is indented

### Defining Functions

```python runnable title="Defining a function does not run it"
def print_header():
    print("Report card")
    print("-" * 20)

print("Nothing has happened yet")
```

Nothing from inside the function was printed. Defining a function only stores it under its name. The two lines inside it will run later, when somebody calls the function.

### Calling Functions

Calling it runs the body.

```python runnable title="Calling runs the body"
def print_header():
    print("Report card")
    print("-" * 20)

print_header()
print("Asha - class 7B")
print_header()
```

The brackets are what make the call happen. A name with no brackets gives you the function itself, not its result:

```python runnable title="With brackets and without"
def print_header():
    print("Report card")

print(print_header)
print_header()
```

The first line printed something like `<function print_header at 0x...>`. That is the function itself. Forgetting the brackets is a common slip, and it produces no error at all.

### Function Parameters

A **parameter** is a name in the definition. An **argument** is the value you pass in when you call it.

```python runnable title="One parameter, three different arguments"
def greet(student):
    print("Hello,", student)

greet("Asha")
greet("Ravi")
greet("Meera")
```

`student` is the parameter. `"Asha"` is an argument.

Think of it this way: the parameter is the empty box labelled in the definition; the argument is what you put in the box when you call.

Several parameters are separated by commas, and the values line up in order:

```python runnable title="The values line up in order"
def report(student, subject, marks):
    print(student, "scored", marks, "in", subject)

report("Asha", "Maths", 78)
```

Passing the wrong number of arguments is caught straight away:

```python runnable expect-error title="Too few arguments"
def report(student, subject, marks):
    print(student, subject, marks)

report("Asha", "Maths")
```

`TypeError: report() missing 1 required positional argument: 'marks'` names the parameter that had no value.

### Return Values

`return` hands a value back to whoever called the function.

```python runnable title="Handing a value back"
def total_of(marks):
    return sum(marks)

result = total_of([78, 85, 60])

print("Total:", result)
print("Doubled:", result * 2)
```

The value arrived at the call, was stored in `result`, and the program went on using it. That is what returning means: the answer comes back to whoever asked.

`print()` shows something to a person. `return` gives a value to the rest of the program. They are not the same, and mixing them up is the most common beginner mistake with functions.

```python runnable title="Printing is not returning"
def shows(marks):
    print(sum(marks))

def gives(marks):
    return sum(marks)

a = shows([78, 85])
b = gives([78, 85])

print("From the printing one:", a)
print("From the returning one:", b)
```

`a` is `None`, because `shows()` printed a number and returned nothing. A function with no `return` always hands back `None`.

`return` also ends the function immediately:

```python runnable title="Nothing after return runs"
def grade_for(marks):
    if marks >= 80:
        return "A"
        print("This line can never run")
    return "Not an A"

print(grade_for(90))
```

The `print` under the first `return` never runs. The moment `return` is reached, the function is finished and the value goes back to the caller.

A function can return several values at once, separated by commas:

```python runnable title="Returning more than one value"
def summarise(marks):
    return min(marks), max(marks), sum(marks) / len(marks)

lowest, highest, average = summarise([78, 85, 60])

print("Lowest:", lowest)
print("Highest:", highest)
print("Average:", round(average, 1))
```

The three values are packed together on the way back and unpacked into three names at the call.

So far, every function has received a fixed set of values.

But functions are not limited to one style of input. Python provides several ways to pass values into a function, from simple positional arguments to more flexible approaches that can accept any number of values.

## Function Arguments

### Function Arguments Overview

There are several ways to pass values in. They exist so a function can stay easy to call as it grows.

<div className="tscroll">

| Kind | Looks like | Meaning |
|---|---|---|
| Positional | `report("Asha", 78)` | matched by order |
| Keyword | `report(student="Asha")` | matched by name |
| Default | `def report(marks=0)` | used when nothing is passed |
| Positional-only | `def f(a, b, /)` | must be given by position |
| Keyword-only | `def f(*, a, b)` | must be given by name |
| `*args` | `def f(*marks)` | any number of positional values |
| `**kwargs` | `def f(**details)` | any number of named values |

</div>

The first three kinds — positional, keyword and default — cover most of the code you will write.

The next few argument styles are used less often. You may not need them in every program, but they are worth recognising, because you will see them in professional Python code. Each one is explained below as you meet it.

### Positional Arguments

**Positional** arguments are matched by their order. The first value goes to the first parameter.

```python runnable title="Order decides everything"
def report(student, subject, marks):
    print(student, "scored", marks, "in", subject)

report("Asha", "Maths", 78)
report("Maths", "Asha", 78)
```

The second call is nonsense, and Python cannot tell. Both values are text, both fit, and nothing errors. Order is the only thing holding it together.

### Keyword Arguments

A **keyword** argument names the parameter it is for, so order stops mattering.

```python runnable title="Named values, any order"
def report(student, subject, marks):
    print(student, "scored", marks, "in", subject)

report(subject="Maths", marks=78, student="Asha")
```

Keywords also make a call readable at a glance:

```python runnable title="Which call explains itself?"
def record(student, marks, attendance, promoted):
    print(student, marks, attendance, promoted)

record("Asha", 78, 92, True)
record(student="Asha", marks=78, attendance=92, promoted=True)
```

In the first call, the reader has to remember what each bare value means. In the second, the names say it.

Positional arguments must come before keyword ones:

```python runnable expect-error title="A positional value after a keyword one"
def report(student, subject, marks):
    print(student, subject, marks)

report(student="Asha", "Maths", 78)
```

### Default Arguments

A **default** gives a parameter a value to use when the caller does not supply one.

```python runnable title="A parameter that can be left out"
def report(student, subject="Maths", marks=0):
    print(student, "scored", marks, "in", subject)

report("Asha")
report("Ravi", "Science")
report("Meera", "English", 92)
```

The first call needed only the student, because `subject` and `marks` had defaults waiting.

Parameters with defaults must come after those without:

```python runnable expect-error title="A default before a required parameter"
def report(subject="Maths", student):
    print(student, subject)
```

One rule is worth learning before it bites you: **never use a list or a dictionary as a default value.**

```python runnable title="A default list is created once, not once per call"
def add_mark(mark, marks=[]):
    marks.append(mark)
    return marks

print(add_mark(78))
print(add_mark(85))
print(add_mark(60))
```

Every call shares one list, because the default was created once when the function was defined. The usual fix is `None`:

```python runnable title="The safe way to default a list"
def add_mark(mark, marks=None):
    if marks is None:
        marks = []
    marks.append(mark)
    return marks

print(add_mark(78))
print(add_mark(85))
```

`None` means "nothing was passed". When the function sees it, it builds a fresh list itself, so no list is ever shared between calls.

A default value is created once, when the function is defined. For a number or a piece of text that is harmless. For a list or a dictionary it means every call shares the same one. Default to `None` and build it inside.

### Positional-Only Arguments

Anything before a `/` in the parameter list **must** be passed by position.

```python runnable expect-error title="A positional-only parameter passed by name"
def divide(a, b, /):
    return a / b

print(divide(10, 2))
print(divide(a=10, b=2))
```

The first call worked. The second failed, because `a` and `b` may not be named.

This is used for parameters whose names carry no meaning for the caller, so the author stays free to rename them later.

### Keyword-Only Arguments

Anything after a bare `*` **must** be passed by name.

```python runnable title="Parameters that must be named"
def record(student, *, promoted, attendance):
    print(student, "promoted:", promoted, "attendance:", attendance)

record("Asha", promoted=True, attendance=92)
```

```python runnable expect-error title="A keyword-only parameter passed by position"
def record(student, *, promoted, attendance):
    print(student, promoted, attendance)

record("Asha", True, 92)
```

This is how you stop a call turning into a row of bare values whose meaning nobody can read.

### Arbitrary Positional Arguments

`*args` collects any number of positional values into one tuple.

```python runnable title="Any number of values"
def total(*marks):
    print("Got:", marks)
    return sum(marks)

print(total(78, 85))
print(total(78, 85, 60, 92))
print(total())
```

The star does the collecting. The name is a convention, not a rule — `*marks` works just as well as `*args`, and reads better.

### Arbitrary Keyword Arguments

`**kwargs` collects any number of named values into a dictionary.

```python runnable title="Any number of named values"
def describe(**details):
    for key, value in details.items():
        print(key, "->", value)

describe(student="Asha", marks=78, section="7B")
```

### Mixing Different Argument Types

They can be combined, but the order in the definition is fixed:

```text
positional, *args, keyword-only, **kwargs
```

```python runnable title="All the kinds in one definition"
def report(student, *marks, promoted=False, **details):
    print("Student:", student)
    print("Marks:", marks)
    print("Promoted:", promoted)
    print("Details:", details)

report("Asha", 78, 85, 60, promoted=True, section="7B", teacher="Meera")
```

`"Asha"` filled the first parameter. The three numbers were collected by `*marks`. `promoted` was named. Everything else landed in `details`.

Most functions need none of this. Reach for it when a function genuinely takes an unknown number of values.

Passing values into a function is only half the story.

We also need to know what happens to variables once they are inside the function. This brings us to scope: the rules that decide where a variable can be seen and used.

## Variable Scope

### What Is Variable Scope?

When programs become larger, different functions often use variables with the same names.

Python needs rules to decide which variable a piece of code should use. Those rules are called **scope**: the part of a program where a name can be seen.

A name created inside a function belongs to that function. Outside it, the name does not exist.

### Local Variables

A **local** variable is one created inside a function.

```python runnable expect-error title="A local name does not escape"
def total_of(marks):
    result = sum(marks)
    return result

print(total_of([78, 85]))
print(result)
```

`NameError` — `result` existed only while the function was running.

This is a feature, not a limit. It means two functions can both use a variable called `result` without ever interfering with each other.

### Global Variables

A **global** variable is created outside every function. Functions can read it.

```python runnable title="A function can read a global"
PASS_MARK = 35

def has_passed(marks):
    return marks >= PASS_MARK

print(has_passed(78))
print(has_passed(20))
```

But assigning to it inside a function creates a **new local** name instead of changing the global one:

```python runnable title="Assigning inside makes a local name"
count = 0

def record():
    count = 99
    print("Inside the function:", count)

record()
print("Outside the function:", count)
```

The global `count` never changed. The function quietly made its own `count`, used it, and threw it away when the call ended.

### The global Keyword

`global` says that a name inside the function refers to the one outside.

```python runnable title="Changing a global on purpose"
count = 0

def record():
    global count
    count += 1

record()
record()
record()

print("Records made:", count)
```

Use it sparingly. A function that changes a global can affect any other part of the program, which makes a bug hard to trace. Returning a value is almost always better:

```python runnable title="The same job without global"
def record(count):
    return count + 1

count = 0
count = record(count)
count = record(count)

print("Records made:", count)
```

Here the function takes the value in and hands the new one back, so every change is visible at the call where it happens.

### The nonlocal Keyword

A function can be defined inside another function. `nonlocal` lets the inner one change a name belonging to the outer one.

```python runnable title="Reaching the enclosing function's name"
def counter():
    count = 0

    def increase():
        nonlocal count
        count += 1

    increase()
    increase()
    return count

print(counter())
```

Without `nonlocal`, `count += 1` would try to make a local name and fail, because it reads the value before setting it.

`global` reaches all the way out to the module. `nonlocal` reaches out one level, to the enclosing function.

### Scope Resolution (LEGB Rule)

When Python meets a name, it looks in four places, in this order:

<div className="tscroll">

| Letter | Scope | Where it is |
|---|---|---|
| **L** | Local | inside the current function |
| **E** | Enclosing | inside the function that contains it |
| **G** | Global | at the top level of the file |
| **B** | Built-in | names Python always provides, like `print` |

</div>

The first match wins, and the search stops there.

```python runnable title="All four levels, innermost first"
student = "global Asha"

def outer():
    student = "enclosing Ravi"

    def inner():
        student = "local Meera"
        print(student)

    inner()
    print(student)

outer()
print(student)
```

Three names, three values, no interference. Each `print` found the nearest one.

This is also why a variable named after a built-in causes trouble:

```python runnable expect-error title="Shadowing a built-in name"
list = [78, 85]
print(list)

numbers = list(range(3))
```

`list` was found in the global scope before Python ever reached the built-ins, so `list(...)` tried to call a list. Avoid naming variables `list`, `sum`, `str`, `type` or `input`.

The practical rule is simple: Python looks for the nearest matching name first. If it cannot find one, it keeps looking further out until it either finds a match or runs out of places to search.

Several of the argument styles you have just seen use symbols such as `*` and `**`. To understand exactly what those symbols are doing, we need to look at packing and unpacking.

## Packing and Unpacking

### What Is Packing?

**Packing** gathers several values into one.

```python runnable title="Several values becoming one tuple"
result = "maths", 78, "A"

print(result)
print(type(result))
```

### Packing with *args

In a function definition, `*` packs the extra positional arguments into a tuple.

```python runnable title="Packing the arguments as they arrive"
def total(*marks):
    print("Packed into:", marks)
    return sum(marks)

print(total(78, 85, 60))
```

### What Is Unpacking?

**Unpacking** is the opposite: taking one collection apart into separate values.

```python runnable title="Taking a tuple apart"
result = ("maths", 78, "A")

subject, marks, grade = result

print(subject)
print(marks)
print(grade)
```

### Unpacking Iterables

A `*` on the left collects everything that is left over.

```python runnable title="First, last, and the rest"
marks = [78, 85, 60, 92, 45]

first, *middle, last = marks

print("First:", first)
print("Middle:", middle)
print("Last:", last)
```

`middle` is always a list, even when it ends up holding one item or none.

### Unpacking Function Arguments

A `*` at the **call** spreads a list out into separate arguments.

```python runnable title="Spreading a list into a call"
def report(student, subject, marks):
    print(student, "scored", marks, "in", subject)

values = ["Asha", "Maths", 78]

report(*values)
```

Without the `*`, the whole list would arrive as a single first argument.

`**` does the same for a dictionary, matching the keys to parameter names:

```python runnable title="Spreading a dictionary into a call"
def report(student, subject, marks):
    print(student, "scored", marks, "in", subject)

details = {"student": "Asha", "subject": "Maths", "marks": 78}

report(**details)
```

So `*` and `**` mean opposite things depending on where they are: in a definition they **pack**, and at a call they **unpack**.

### Multiple Assignment Unpacking

The same mechanism handles the everyday swap:

```python runnable title="Swapping two values with no temporary"
first = 78
second = 85

first, second = second, first

print(first, second)
```

The right-hand side is packed into a pair first, then unpacked into the two names. No third variable is needed.

A quick way to remember it:

- In a function definition, `*` and `**` collect values together.
- In a function call, `*` and `**` spread values apart.

So far, we have been writing our own functions.

However, Python already includes many useful functions that are ready to use without writing them yourself.

## Built-in Functions

### What Are Built-in Functions?

A **built-in function** is one Python provides without any import. You have been using them since the first lesson.

```python runnable title="Built-ins you have already met"
marks = [78, 85, 60]

print(len(marks))
print(sum(marks))
print(max(marks))
print(min(marks))
print(sorted(marks))
```

### Benefits of Built-in Functions

They are already written, already tested, and faster than anything you would write yourself. Reaching for one is almost always better than writing the loop by hand.

```python runnable title="By hand, and with a built-in"
marks = [78, 85, 60]

total = 0
for mark in marks:
    total += mark
print("By hand:", total)

print("Built-in:", sum(marks))
```

### Categories of Built-in Functions

<div className="tscroll">

| Group | Functions |
|---|---|
| Numbers | `abs()`, `round()`, `pow()`, `sum()`, `min()`, `max()` |
| Types | `int()`, `float()`, `str()`, `bool()`, `list()`, `dict()`, `type()` |
| Collections | `len()`, `sorted()`, `reversed()`, `enumerate()`, `zip()`, `range()` |
| Checking | `isinstance()`, `all()`, `any()` |
| Input and output | `print()`, `input()` |
| Finding out | `dir()`, `help()` |

</div>

```python runnable title="Three worth knowing early"
marks = [78, 85, 60]
subjects = ["Maths", "Science", "English"]

print(all(mark >= 35 for mark in marks))
print(any(mark >= 80 for mark in marks))

for subject, mark in zip(subjects, marks):
    print(subject, mark)
```

`all()` asks whether every item passes. `any()` asks whether at least one does. `zip()` walks two collections side by side.

Functions already work without any extra information.

However, as programs grow larger, it becomes useful to describe what values a function expects and what it returns. Function annotations help with this.

## Function Annotations

### What Are Function Annotations?

An **annotation** labels what a parameter is expected to hold and what the function gives back.

Python does **not** check them. They are notes for people and for tools.

### Parameter Annotations

```python runnable title="Labelling what goes in"
def greet(student: str):
    print("Hello,", student)

greet("Asha")
greet(78)
```

The second call passed a number where the annotation says text, and Python ran it anyway. The label is a note, not a rule.

### Return Type Annotations

An arrow labels what comes back.

```python runnable title="Labelling what comes out"
def grade_for(marks: int) -> str:
    if marks >= 80:
        return "A"
    return "B"

print(grade_for(90))
```

### Accessing Function Annotations

Python stores them, so tools can read them.

```python runnable title="The annotations are kept"
def report(student: str, marks: int) -> str:
    return f"{student}: {marks}"

print(report.__annotations__)
```

### Benefits of Function Annotations

- The signature says what is expected, with no comment needed.
- Editors use them for better suggestions and warnings.
- Checking tools such as mypy can find mismatches before the code runs.
- They survive. A comment drifts out of date; an annotation sits in the signature where it is read.

```python runnable title="An annotated function reads like a contract"
def average_of(marks: list[int]) -> float:
    return sum(marks) / len(marks)

print(average_of([78, 85, 60]))
```

Anyone can see it takes a list of whole numbers and gives back a decimal, without reading the body.

Annotations help describe the values a function expects.

Docstrings take that idea further by allowing you to describe the purpose of the function itself.

## Docstrings

### What Is a Docstring?

A **docstring** is a piece of text, written as the first thing inside a function, that says what the function does.

It is not a comment. Python keeps it, and tools show it.

```python runnable title="A function that describes itself"
def grade_for(marks):
    """Return the letter grade for a mark out of 100."""
    if marks >= 80:
        return "A"
    if marks >= 60:
        return "B"
    return "C"

print(grade_for(78))
print(grade_for.__doc__)
```

### Writing Docstrings

Use triple quotes, even for one line, so it can grow later.

For anything beyond the obvious, say what goes in and what comes back:

```python runnable title="A longer docstring"
def average_of(marks):
    """Return the average of a list of marks.

    marks: a list of numbers, which must not be empty.
    Returns the average as a decimal number.
    """
    return sum(marks) / len(marks)

print(average_of([78, 85, 60]))
print(average_of.__doc__)
```

Say **why** and **what**, not how. The body already says how.

```python runnable title="Which docstring earns its place?"
def grade_for(marks):
    """Check the marks with if statements and return a letter."""
    return "A" if marks >= 80 else "B"

def promoted(marks, attendance):
    """Decide whether a student moves up a year.

    A student is promoted only when they pass the exam and
    attend at least 75 percent of classes, which is the rule
    agreed with the class teachers.
    """
    return marks >= 35 and attendance >= 75

print(grade_for(90), promoted(78, 92))
```

The first repeats what anyone can see. The second records a rule that comes from outside the code, which is exactly what a reader cannot work out for themselves.

Write the docstring before the body. If you cannot say in one sentence what the function returns, the function is probably doing two things.

## Common mistakes

Each row is one mistake. The middle column is what you will actually see on screen.

<div className="tscroll">

| Mistake | What you see | Why it happens | Fix |
|---|---|---|---|
| Printing instead of returning | The caller gets `None` | `print()` shows a value; `return` hands it back | Use `return`, and print the result at the call |
| Calling without brackets | `<function grade_for at 0x...>` | The name alone is the function, not a call | `grade_for(78)` |
| Wrong number of arguments | `TypeError: missing 1 required positional argument` | Every parameter without a default needs a value | Pass all of them, or give defaults |
| A list as a default value | Values pile up between calls | The default is created once, at definition | Default to `None` and build it inside |
| A default before a required parameter | `SyntaxError: non-default argument follows default argument` | Required parameters must come first | Move the defaults to the end |
| A positional value after a keyword one | `SyntaxError: positional argument follows keyword argument` | Positional values must come first | Reorder the call |
| Assigning to a global inside a function | The global never changes | Assignment makes a new local name | Return a value, or use `global` deliberately |
| Using a local name outside its function | `NameError: name 'result' is not defined` | A local name ends with the call | Return it |
| Naming a variable `list` or `sum` | `TypeError: 'list' object is not callable` | The global name is found before the built-in | Pick another name |
| Expecting annotations to be enforced | A wrong type passes straight through | Python does not check annotations | Use a checker such as mypy |
| Code after a `return` | A line that never runs | `return` ends the function at once | Move it above the `return` |

</div>

## What to Remember

- **A function is a named piece of work you can run whenever you need it.**
- `def` defines it; brackets call it. A name without brackets is the function itself, not its result.
- A parameter is the name in the definition; an argument is the value passed in.
- **`print()` shows a value; `return` hands it back.** A function with no `return` gives `None`.
- Positional arguments are matched by order, keyword arguments by name.
- A default lets a parameter be left out, and defaults come after required parameters.
- **A list or dictionary default is created once and shared by every call.** Default to `None`.
- `*args` collects extra positional values; `**kwargs` collects extra named ones.
- `*` and `**` pack in a definition and unpack at a call.
- A name created in a function is local and disappears when the call ends.
- Reading a global needs nothing; changing one needs `global`, and returning a value is usually better.
- Python finds names Local, Enclosing, Global, Built-in — in that order, first match wins.
- Annotations label what goes in and comes out, and **Python does not enforce them**.
- A docstring says what a function does and why, and Python keeps it.

Next: **Code Organization**
