Believemy logo purple

Functions in Python

The def keyword is used to create named functions in Python.
Believemy logo

The def keyword is a pillar of Python programming.

It allows you to define named functions, which are blocks of code that can be reused multiple times in a program.

Using functions allows you to:

  • Structure your code in a modular way;
  • Reuse instructions without repeating them;
  • Improve readability while increasing maintainability.

The def keyword is therefore the gateway to cleaner, more efficient, and more scalable programming.

 

Basic syntax with def

The basic syntax of a function in Python is very simple:

PYTHON
def function_name(parameters):
    instruction(s)
    return result

Here are the key elements of a function with Python:

  • def is the keyword used to define a function;
  • We follow with the function name, then a list of parameters in parentheses;
  • The instruction block is indented (often 4 spaces).

Finally, we can find the return keyword which allows you to return a value when the function is called.

In Python, indentation is mandatory. This is what delimits the block of instructions belonging to the function. 😉

 

Simple function example with def

Let's take a very simple function example:

PYTHON
def addition(x, y):
    return x + y

result = addition(3, 5)
print(result)  # Result: 8

In this example:

  • def addition(x, y) creates a function called addition that takes two parameters;
  • It returns the sum of the two (x + y);
  • We call our function using its name followed by the arguments we want to give it with addition(3, 5) which returns 8, which we store in a variable result;
  • Finally, print() displays this result.

A function can be called multiple times with different arguments. This is what makes it a powerful tool for automating repetitive operations. 😋

 

Parameters and arguments

As we saw quickly in our previous example, functions defined with def can receive parameters, which are input values that influence their behavior.

Types of parameters in Python

Positional parameters

They are passed in the defined order:

PYTHON
def greet(first_name, last_name):
    return f"Hello {first_name} {last_name}"

print(greet("Alice", "Smith")) # Hello Alice Smith

As we can see, the first value given "Alice" will be automatically assigned to the first_name variable of our function. Same for "Smith" which will go to the argument in second position.

This means that if we reverse "Alice" and "Smith", the first name and last name of our user will be... reversed! 😬

PYTHON
def greet(first_name, last_name):
    return f"Hello {first_name} {last_name}"

print(greet("Smith", "Alice")) # Hello Smith Alice

Here "Smith" goes into the first_name variable, while "Alice" goes into the last_name variable. This is what we call positional parameters.

 

Named arguments

Unlike positional parameters, they allow you to specify the value of a parameter, regardless of order:

PYTHON
def greet(first_name, last_name):
    return f"Hello {first_name} {last_name}"

print(greet(last_name="Smith", first_name="Alice"))
# Hello Alice Smith

Here, we won't have any reversal problems!

 

Default values

Finally, we can assign a default value to a parameter:

PYTHON
def greet(first_name, last_name="Johnson"):
    return f"Hello {first_name} {last_name}"

print(greet("Julie"))  # Hello Julie Johnson

Parameters with default values must always be placed after mandatory parameters.

 

Returning a value with return

The return keyword allows a function to return a value. This is what differentiates a function that "returns" from a function that "does" something.

It's generally used to:

  • Get a result calculated by the function;
  • Chain multiple functions together;
  • Separate processing and display.

Let's take a small example:

PYTHON
def square(x):
    return x ** 2

result = square(4)
print(result)  # 16

A function can contain multiple returns, which allows returning different results based on conditions.

PYTHON
def evaluation(grade):
    if grade >= 10:
        return "Passed"
    else:
        return "Failed"

 

Functions that never return anything

Unlike what we just saw, not all functions return an explicit value.

Sometimes, a function modifies an existing object or performs an external action (display, file writing, etc.).

Let's take another example to illustrate all this:

PYTHON
def say_hello(name):
    print(f"Hello {name}")

This function directly displays a string, without ever returning anything!

 

Type annotations on functions

Python allows adding type annotations to parameters and function return. This doesn't change the function's behavior, but facilitates code reading and allows editors or linter tools to analyze expected types.

Here's an example:

PYTHON
def greet(first_name: str, age: int) -> str:
    return f"Hello {first_name}, you are {age} years old."

Type annotations indicate:

  • that first_name is a string;
  • that age is an integer;
  • that this function returns a string.

Warning, these annotations are not mandatory and are not executed. Python doesn't check types at runtime. They're very similar to what we can find with TypeScript.

Typing helps to improve code documentation, work better as a team while avoiding errors with tools like MyPy.

 

Docstrings and documentation

It's common to see docstrings when talking about functions! 😉

A docstring (for documentation string 🇺🇸) is a comment placed just after the definition of a function, intended to describe what this function does.

Docstring syntax

PYTHON
def multiply(a: int, b: int) -> int:
    """Multiplies two integers and returns the result."""
    return a * b

As we can see in our example, a docstring must be:

  • delimited by three double quotes """ or three single quotes ''';
  • written in a clear and concise manner;
  • respect the conventions of PEP 257.

 

Complete example of a docstring on a function

PYTHON
def calculate_rectangle_area(length: float, width: float) -> float:
    """
    Calculates the area of a rectangle.

    Parameters:
    - length: float, the length of the rectangle
    - width: float, the width of the rectangle

    Return:
    - float: the calculated area
    """
    return length * width

Not bad, right? 😊

Docstrings are automatically read by help() and IDEs (code editors). They greatly improve code understanding, especially in collaborative work.

 

Variable scope in a function (scope)

The scope of a variable refers to the place in the program where this variable is accessible.

In Python, variables defined in a function are not accessible outside.

Local variables

This is a variable created inside a function.

PYTHON
def say_hello():
    message = "Hello"
    print(message)

print(message) # Error because message only exists for say_hello()

 

Global variables

A variable defined outside any function is global, so visible throughout the program... but not modifiable from a function without using the keyword global.

PYTHON
name = "Alice"

def change_name():
    global name
    name = "Bob"

change_name()
print(name)  # Bob

The use of global is often discouraged because it makes code harder to follow. It's preferable to return a value and store it.

 

Nested functions

In Python, it's possible to define a function inside another function. This is exactly what we call a nested function. 👀

Let's take an example:

PYTHON
def outer():
    def inner():
        return "Hello from the inside!"
    return inner()

In our example, you need to call outer() which will call inner() to return our string.

Ok that's great, but what's it for?

For many things! 🥸

We can for example:

  • encapsulate logic that will only be used in a specific place;
  • create closures;
  • improve readability in certain complex cases.

 

Recursive functions

As we've seen, functions can be nested.

Thanks to this possibility, we can create what are called recursive functions.

A recursive function is a function that calls itself. It's useful for solving problems in a decomposed manner, like factorial calculations, sequences, or tree traversals.

Here's a very classic example:

PYTHON
def factorial(n):
    if n == 0:
        return 1
    else:
        return n * factorial(n - 1)

print(factorial(5))  # 120

Poorly controlled recursion can cause a stack overflow (RecursionError). Make sure you have an exit case. Otherwise, you'll create an infinite loop (which will crash your code)!

 

Functions are objects

In Python, functions are considered as objects.

This means it's possible to assign them to variables, pass them as parameters, or even store them in lists for example.

Here's each case in small examples!

Assigning a function to a variable

PYTHON
def hello():
    return "Hi!"

say = hello
print(say())  # Hi!

Here, we assign the hello function to our say variable. In other words, we can now execute say as a function!

 

Passing a function as an argument

PYTHON
def apply(function, value):
    return function(value)

def double(x):
    return x * 2

result = apply(double, 5)
print(result)  # 10

In this example, we pass the double function to our apply function: this is very useful to decompose our functions into several small functions.

 

Storing in a list

PYTHON
def a(): return "A"
def b(): return "B"

functions = [a, b]
for f in functions:
    print(f())  # A then B

In this example, we assign functions a and b to a list to be able to reuse them later (in a loop here).

 

Generator functions with yield

It's also possible to use what are called generators.

These are special functions that use the yield keyword instead of return.

They always return one element at a time, which allows managing large volumes of data without loading everything in memory. Small bonus: they preserve execution state between each call!

For example:

PYTHON
def count():
    for i in range(3):
        yield i

for number in count():
    print(number)  # 0, 1, 2

yield suspends function execution and resumes it at the same place on the next call.

 

Decorators on functions

Decorators are functions that modify or enrich another function without modifying it directly. They are widely used in frameworks like Flask or Django.

They take a function as an argument and return a modified function.

They are widely used in contexts like logging, input validation, or authentication.

Let's take a very simple example: imagine you want to display "== Start ==" and "== End ==" at each function call without changing its code. Rather than modifying all your functions, you use a decorator.

PYTHON
def display_tags(function):
    def wrapper():
        print("== Start ==")
        function()
        print("== End ==")
    return wrapper

@display_tags
def say_hello():
    print("Hello everyone!")

say_hello()

What happens behind the scenes:

  • def display_tags(function): a function that takes another function as a parameter;
  • inside, we define wrapper() which calls the original function while adding code before and after;
  • by applying @display_tags before another function, Python executes our decorator by passing the say_hello() function defined right after as an argument!

Decorators therefore make our code more modular and reusable, while keeping your functions clean.

 

Anonymous functions (lambda) vs named functions

In Python, there are two main ways to define functions:

  • with anonymous functions also called lambda functions;
  • or with named functions which we also call traditional functions (what we're seeing here).

Comparison table

Function typeSyntaxMain use case
Named (def)def name():Factorization and documentation
Anonymous (lambda)lambda args: exprOne-time and unique operations

 

Compared examples

PYTHON
# Named function
def add(x, y):
    return x + y

# Equivalent lambda function
lambda_add = lambda x, y: x + y

Prefer def when the function is complex, long, or used multiple times.

 

Best practices for writing with def

Several best practices are necessary to make functions that respect conventions in Python.

To start, you should use clear names and avoid names that are too vague:

  • calculate_triangle_area(base, height) is very representative;
  • c(x, y) is far too abstract.

You should also respect the PEP 8 standard, in other words:

  • you should put two blank lines between function definitions (except in classes);
  • and you should name functions with words in lowercase separated by underscores.

 

Common errors with def

Let's take a quick tour of the most common errors when creating functions with def in Python.

Forgetting the return

PYTHON
def calculate(x, y):
    x + y  # Nothing is returned!

print(calculate(2, 3))  # Displays: None

You need to remember to add the return keyword:

PYTHON
def calculate(x, y):
    return x + y

 

Having bad indentation

PYTHON
def test():
print("Hello")  # Indentation error!

Bad indentation prevents Python from understanding when a function ends.

PYTHON
def test():
    print("Hello")

 

Forgetting parentheses to call a function

PYTHON
def hello():
    return "Hi"

print(hello)   # Error

This is a classic error! 👀

PYTHON
def hello():
    return "Hi"

print(hello())   # Parentheses are added

 

Frequently asked questions about def

Can we have multiple returns?

Yes. This allows returning different values based on conditions.

 

Should we type all functions?

No, as we've seen together, type annotations are optional.

 

What is a recursive function?

A function that calls itself. It allows treating problems in a decomposed manner.

 

How to learn Python?

By joining our Python course!

Discover our python glossary

Browse the terms and definitions most commonly used in development with Python.