Search⌘ K
AI Features

Defining and Calling Functions

Explore how to define and invoke functions in Python to organize code into reusable blocks. Understand function parameters versus arguments, returning values, local variable scope, recursion basics, and PEP 8 naming conventions. This lesson equips you to write clearer, modular, and maintainable Python programs.

Repeating the same code in multiple places is a clear warning sign in programming. When we copy and paste logic, e.g., calculating a tax rate or formatting a user’s name, we increase the risk of errors. If that logic needs to change later, every duplicated instance must be found and updated, which is both time-consuming and error-prone.

Python functions solve this problem by allowing us to give a name to a block of code and reuse it wherever it is needed. By encapsulating logic inside functions, we make our programs more modular, readable, and easier to maintain.

What are functions

In Python, a function is a named block of code that groups related instructions together. The code inside a function does not run immediately; instead, it runs only when the function is explicitly called.

We define a function using the def keyword. If you are learning how to define a function in Python, remember that the signature is followed by parentheses and a colon (:). The function’s body appears on the next line and is indented, just like the code inside conditionals and loops.

def <function_name>():
# Function's body

Working with functions involves two steps: defining the function and calling it. A function must be defined before it can be called, since Python executes code from top to bottom.

We’ll begin with the simplest type of function: one that takes no input and produces no output.

Python 3.14.0
def welcome_message():
"""Prints a standard welcome message."""
print("Welcome to the System.")
print("Initializing modules...")
# The function is defined above, but runs here
welcome_message()
  • Line 1: We use the def keyword to create the function signature. This establishes the name welcome_message as a reusable identifier for the logic that follows.

  • Line 2: We include a docstring (documentation string). This is best practice for explaining what the function does, helping other developers understand the tool without having to read every line of code.

  • Lines 3–4: This is the function body, indented to show ownership. These instructions are stored in memory but do not run yet; they are merely the "recipe" waiting to be used.

  • Line 7: We call (invoke) the function. Defining the function merely created the plan; this line actually executes it. By using the function name followed by parentheses (), we trigger the logic defined in the block above.c

Arguments vs. parameters

Most functions require input data, often referred to as function arguments, in order to do useful work. When we define a function, we specify the names of the inputs it expects inside the parentheses. These names are called parameters. Parameters do not yet hold real values; they are placeholders that describe the information the function will receive.

When we call the function, we provide the actual values. These values are called arguments. By default, Python matches arguments to parameters by position, assigning them in the same order the parameters are listed. This system allows a single function to reuse the same logic while operating on different data each time it is called.

Python 3.14.0
def create_greeting(name):
"""Generates a personalized greeting."""
message = f"Hello, {name}! We are glad you're here."
print(message)
# Calling the function with different arguments
create_greeting("Alex")
create_greeting("Taylor")
  • Line 1: We define the function with one parameter, name, which acts as a variable inside the function body.

  • Line 3: We create a personalized message using the value passed into the name parameter.

  • Line 7: When we call create_greeting("Alex"), the string "Alex" is assigned to the parameter name.

  • Line 8: The same function is called again, but this time, the value "Taylor" is passed, resulting in a different output.

Note: You can also define Python default arguments by assigning a value to a parameter in the function signature (e.g., def greet(name="User"):). This allows the function to run even if the caller doesn't provide an argument for that specific parameter.

Returning results with return

The previous examples displayed results directly using print(). However, in professional code, functions usually return a value using the return statement, so the caller can use the result in further computations. When we use return, execution stops. While this example returns one value, you will eventually learn how to return multiple values from a function in python by separating them with commas.

A function that returns a value is often much more flexible than one that just prints. If a function does not contain an explicit return statement, Python automatically returns the special value, None, at the end of execution.

Let's look at a function that calculates an area and returns the result.

Python 3.14.0
def calculate_area(length, width):
"""Calculates the area of a rectangle and returns the result."""
area = length * width
return area
# 1. Call the function and store the returned result in a variable
pool_area = calculate_area(length=10, width=5)
# 2. Use the result in another calculation
tile_cost = pool_area * 15
print(f"Pool Area: {pool_area} sq meters")
print(f"Total tile cost: ${tile_cost}")
  • Line 1: The function is defined to accept two parameters: length and width.

  • Line 3: The area is calculated and stored in the local variable area.

  • Line 4: The return statement sends the value stored in area back to the caller.

  • Line 7: We call the function. The values 10 and 5 are passed as arguments. The calculated area (50) is returned and stored in the variable pool_area.

  • Line 10: The returned value is then used in a new calculation, demonstrating the reusability of the returned result.

Function execution flow and variable scope

Let's try an experiment. In the code below, we define a variable inside a function and then try to print it from the main program.

Run this code and observe the error:

Python
def process_data():
result = 20 # Defined inside the function
return result
final_output = process_data()
print(final_output)
# ERROR: 'result' does not exist here!
print(result)

  • Line 2: The variable result is defined inside the process_data function. Its scope is local to that function.

  • Line 5: We call the function, and the value 20 is returned and stored in the global variable final_output.

  • Line 6: We can successfully print the value stored in final_output.

  • Line 9 : This line, Python raises a NameError. This is because the variable result was destroyed when process_data finished executing, so it is unknown in the outside scope.

Why did this fail?

This error occurs because of how Python manages memory. When we call a function, Python performs the following steps:

  1. Pauses execution in the main program.

  2. Jumps to the defined function, creating a new, isolated environment called a local scope.

  3. Executes the code in the function body.

  4. Destroys the local scope (including variables like result) as soon as the function exits.

  5. Resumes execution at the point of the original call.

The isolation of the local scope is key: variables defined inside a function cannot be accessed from outside it. This prevents accidental data changes and keeps functions predictable.

Functions calling themselves: Recursion

Sometimes, the most elegant way to solve a complex problem is to break it down into smaller, identical problems. In programming, we achieve this through Python recursion, which occurs when a function calls itself from within its own body.

While loops allow for standard iteration, recursion provides a unique approach to repeating code, especially when dealing with hierarchical data or complex math. However, just like an infinite loop, a recursive function will run forever if we don't tell it when to stop. Therefore, every recursive function relies on a Python if else statement to divide the logic into two strict parts:

  1. The base case: The condition that stops the recursion (the simplest instance of the problem).

  2. The recursive case: The part where the function calls itself with a modified argument, inching closer to the base case.

Let's look at a simple script that counts down to zero using recursion rather than a standard loop.

Python
def countdown(n):
if n <= 0:
print("Liftoff!")
else:
print(n)
countdown(n - 1)
countdown(3)
  • Line 1: We define the function to accept a single numerical parameter, n.

  • Line 2: This is our base case. We use the if condition to check if n has reached 0 or dropped below it. If it has, the recursion stops, and we print the final message.

  • Line 4: This is our recursive case, handled by the else block. If the number is greater than 0, we print the current number.

  • Line 6: The magic happens here. The countdown function pauses its current execution and calls itself, passing n - 1 as the new argument. This creates a chain reaction: countdown(3) calls countdown(2), which calls countdown(1), until the base case is met and the chain unwinds.

PEP 8 naming conventions for functions

Good naming is essential for writing readable Python code. According to PEP 8, Python's official style guide, function names should adhere to the following convention:

  • Lowercase with words separated by underscores. This is known as snake_case.

  • The name should be a concise, action-oriented verb (e.g., calculate, get, draw).

Examples:

  • calculate_total (Standard Python style)

  • CalculateTotal (Reserved for Classes)

  • calculateTotal (Common in Java/JS, but not Python)

We use these guidelines so that any other Python developer can immediately understand the purpose of our functions.

By defining functions, using clear names, and passing arguments, we can organize our programs into logical, testable units that greatly improve our ability to solve complex problems. We've built the foundation for modular Python development!