Functions in Python
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:
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:
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 calledaddition
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 variableresult
; - 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:
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! 😬
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:
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:
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:
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.
PYTHONdef 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:
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:
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
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
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.
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
.
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:
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:
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
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
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
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:
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.
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 thesay_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 type | Syntax | Main use case |
Named (def ) | def name(): | Factorization and documentation |
Anonymous (lambda ) | lambda args: expr | One-time and unique operations |
Compared examples
# 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
def calculate(x, y):
x + y # Nothing is returned!
print(calculate(2, 3)) # Displays: None
You need to remember to add the return
keyword:
def calculate(x, y):
return x + y
Having bad indentation
def test():
print("Hello") # Indentation error!
Bad indentation prevents Python from understanding when a function ends.
def test():
print("Hello")
Forgetting parentheses to call a function
def hello():
return "Hi"
print(hello) # Error
This is a classic error! 👀
def hello():
return "Hi"
print(hello()) # Parentheses are added
Frequently asked questions about def
Can we have multiple
return
s?
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!