Skip to main content
Back to module
Python Fundamentals · Loops and Functions

Writing functions

Reading42mLesson 2 of 3 in this module

Define reusable functions with parameters, default values and return values.

Why it matters

As programs grow, you find yourself writing the same few lines in several places: converting units, validating a value, formatting a report line. A function gives those lines a name so you can write them once and use them everywhere. Functions also make code easier to test, because you can check one small piece at a time, and easier to read, because a good name like celsius_to_fahrenheit explains itself.

Defining and calling a function

def celsius_to_fahrenheit(celsius):
    fahrenheit = celsius * 9 / 5 + 32
    return fahrenheit

print(celsius_to_fahrenheit(100))   # 212.0
print(celsius_to_fahrenheit(-40))   # -40.0

The parts:

  • def starts the definition, followed by the function's name.
  • Inside the parentheses are the parameters: names for the inputs. Here, celsius.
  • The colon and the indented block form the body.
  • return sends a value back to whoever called the function, and ends the function immediately.

Defining a function does not run it. It runs only when you call it, writing its name with arguments in parentheses: celsius_to_fahrenheit(100). The value 100 is assigned to the parameter celsius for that call.

return versus print

This difference trips up almost every beginner:

def area_print(width, height):
    print(width * height)

def area_return(width, height):
    return width * height

a = area_print(3, 4)    # shows 12, but a is None
b = area_return(3, 4)   # shows nothing, but b is 12
print(b * 2)            # 24

print shows a value on the screen and then the value is gone. return hands the value back so the rest of your program can use it. A function with no return statement returns the special value None. As a rule, functions that calculate something should return it and let the caller decide whether to print it.

Several parameters and default values

def bmi(mass_kg, height_m):
    return mass_kg / height_m ** 2

print(round(bmi(70, 1.75), 1))    # 22.9

Arguments are matched to parameters by position. You can also pass them by name, which is clearer when there are several:

print(round(bmi(height_m=1.75, mass_kg=70), 1))   # 22.9

A parameter can have a default value, used when the caller leaves it out:

def format_temp(value, unit="C", decimals=1):
    return f"{value:.{decimals}f} °{unit}"

print(format_temp(21.456))               # 21.5 °C
print(format_temp(70.8, "F"))            # 70.8 °F
print(format_temp(21.456, decimals=2))   # 21.46 °C

Parameters with defaults must come after the ones without.

Returning early and returning several values

return can appear more than once. The first one reached wins:

def safe_divide(a, b):
    if b == 0:
        return None          # nothing sensible to return
    return a / b

To return several values, separate them with commas. Python packs them into a tuple, and the caller can unpack them:

def min_max(values):
    return min(values), max(values)

low, high = min_max([7, 2, 9, 4])
print(low, high)     # 2 9

Scope: variables inside functions

Variables created inside a function are local: they exist only while the function runs and cannot be seen from outside.

def add_tax(price):
    tax = price * 0.2
    return price + tax

print(add_tax(10))   # 12.0
print(tax)           # NameError: name 'tax' is not defined

This is a feature. It means a function cannot accidentally overwrite your other variables, and you can understand it on its own. Pass what a function needs in as arguments, and get results out with return, rather than relying on variables defined elsewhere.

Documenting a function

A string on the first line of the body is a docstring. It describes what the function does, and tools such as help() and code editors show it:

def average(values):
    """Return the mean of a list of numbers, or 0.0 for an empty list."""
    if not values:
        return 0.0
    return sum(values) / len(values)

A worked example: building from small pieces

Small functions combine into bigger ones:

def average(values):
    if not values:
        return 0.0
    return sum(values) / len(values)

def report(name, values):
    return f"{name}: mean {average(values):.1f} from {len(values)} readings"

print(report("Tank A", [20.5, 21.0, 22.5]))
# Tank A: mean 21.3 from 3 readings

Each function does one job and can be tested on its own. That is the habit that makes larger programs manageable.

Common mistakes

  • Forgetting the parentheses. celsius_to_fahrenheit without () refers to the function itself; it does not call it.
  • Printing instead of returning. If the caller needs the value, return it.
  • Code after return. Lines after a return in the same block never run.
  • Calling before defining. Python runs top to bottom, so define a function above the line that first calls it.
  • Mutable default values. def add(item, items=[]) shares one list across every call. Use items=None, then if items is None: items = [].

Quick recap

  • def name(parameters): defines a function; calling name(arguments) runs it.
  • return sends a value back; without it, a function returns None.
  • Default values make parameters optional; keyword arguments make calls readable.
  • Variables inside a function are local to it.
  • Small, single-purpose functions are easier to test and reuse.

Try it

In the Do tab you will write a unit converter, a safe average and a formatter that uses a default value.

Also in this lesson: