Functions & Modularity
Lesson 3 of 8
3Functions & Modularity
The big pictureWrapping repeated logic in a function that returns a value instead of printing it, splitting code across importable files, and what the __main__ guard is for.
What the picture shows: Calling area(3, 4) opens a frame holding w and h, the value is computed and comes back through return, then the frame disappears and the result stays in result.
Map of this part
The concepts in order, click any one to jump to it:
Two ways into the same idea
A function is like a recipe card: give it the ingredients and it gives you the dish, and you reuse it whenever you need without rewriting it. A module is a whole recipe book in a file that you borrow from. A good recipe hands you the dish rather than eating it in front of you (it returns the result instead of printing it).
def defines a function with parameters and default values, and return gives back a usable value. Each .py file is a module imported with import or from x import y. The if __name__ == "__main__": guard stops test code from running when the file is imported elsewhere.
Before You Start
🔁The control flow lesson
You write if and for and understand indentation.
🧮A function in maths
Something that takes an input and gives an output, like doubling a number.
The Core Idea
When the same work repeats in two places, you put it in a function and call it by name. When someone else wrote a useful function, you import it. That is this whole part.
1Define a function and call it
def defines a function with a name and an input in brackets. return hands the result back to whoever called. Defining a function does not run it; calling it by name does.
def greet(name):
return f"Hello {name}"
message = greet("Khalid")
print(message)
print(greet("Noura"))
We called it twice with two inputs and got two results. A result can be kept in a variable or printed directly.
2A default value
Give an input a value in the definition and it becomes optional. The caller may mention it or leave it out.
def area(width, height=1):
return width * height
print(area(3, 4))
print(area(5))
area(5) used the default height=1 and gave 5, while area(3, 4) gave 12.
3Import what others wrote
import brings in a ready-made module. math ships with Python and holds roots and constants. You reach inside with a dot: module name, dot, function name.
import math print(math.sqrt(16)) print(math.pi) print(round(math.pi, 2))
You did not write the square root yourself. Most of your programming will be like this: find the right module and use it.
return and printing inside the function instead. Printing only shows; return gives you a value you can keep and compute with. A function without return returns None.How It Works, Step by Step: What happens when you call a function
Python works out the inputs first, such as 3 and 4.
It opens a new frame and binds each input to its name.
It runs the body until it reaches return or the end.
It closes the frame and puts the value where the call was.
Going deeper
Functions wrap repeated logic into a reusable unit. Modules organize these functions across separate, importable files.
print vs. return
print() displays a value on screen only and can't be used later in the code. return outputs a value from the function that can be stored and processed by your program.
Parameters and Default Values
A function receives inputs (parameters) and may give them default values used when nothing is passed.
Importing Between Files
from utils import greet makes the greet function defined in utils.py available in your current file. Mechanically, Python opens utils.py and executes all of its top-level code from top to bottom exactly once (function and variable definitions), then caches the whole module internally; any later import of it from another file in the same run just retrieves that cached copy instead of re-executing it.
The if __name__ == "__main__" Guard
When you run the file directly, the value of __name__ is "__main__". When it's imported from another file, the value of __name__ is the file's own name.
Diagram 4, the same file, two different ways to run it: Python executes all of the module's code either way, but the value of __name__ differs depending on how it started, and that alone is what triggers the guard or skips it.
## utils.py — a reusable module
def calculate_area(length, width=1):
"""Returns the area of a rectangle; width defaults to 1 (a line)."""
return length * width
def greet(name="friend"):
print(f"Hello, {name}!")
if __name__ == "__main__":
# this part only executes when utils.py is run directly,
# and is completely ignored when imported from another file
print("Quick module test:", calculate_area(4, 3))
## main.py — imports from utils.py
from utils import calculate_area, greet
greet("Muath")
area = calculate_area(5, 2) # we store the result thanks to return
print(f"Area: {area}")
print(x) with return x inside a function, then trying to use its result: result = my_func() will be None if the function only prints and doesn't explicitly return anything.
Check your understanding: if you import utils.py in main.py, will the "Quick module test" message print?
No. Because __name__ equals "utils" when imported, not "__main__", so the guard condition isn't met and that code never executes, this is exactly why this guard exists: separating "self-testing" code from reused code.
Worked example
def with_tax(price, rate=0.15): return price * (1 + rate). Now with_tax(100) gives 115.0, and changing the rate happens in one place.Myth vs Reality
| Myth | Reality |
|---|---|
| print and return do the same thing. | print only shows on the screen; return hands back a value you can store in a variable or use in another sum. |
| A variable inside a function can be seen from outside. | It lives in the function's frame and disappears with it. Whatever you need outside, return it. |
| One long function that does everything is easier than several. | Easier only the first time you write it. Small functions with clear names are tested, fixed and reused one at a time. |
Try it yourself
A function contains print(x * 2) and no return. You write y = double(4). What is y?
The function printed 8 on screen but returned nothing, so y received None. Printing and returning are different things: one is for your eyes, the other for the program.
Quick Review Flashcards
Check Your Understanding
What does a function without return give back?
It gives back None.
What is if __name__ == "__main__": for?
It makes the code below run only when the file is run directly, not when another file imports it.
A function def greet(name, msg="Hi"): how do you call it with only a name?
greet("Sara"); msg takes its default value.
Resources for this part
Sorted by kind: start with the official docs when you want the precise reference, or with an article or video when you want the explanation first.
Resources for this lesson
Grouped by type. Start with the official docs for precision, or an article or video for the explanation.
Official docs2
Done with this lesson?
Sign in to save your progress.