Step 44. Functions and Modules — Building and Assembling Parts

Step 44. Functions and Modules — Building and Assembling Parts

Level 1 — Programming and the Computer’s Inner Workings | Difficulty ★★☆☆☆ | Estimated time: 3 hours

Prerequisites: Steps 41–43 complete. You can use variables, conditionals, loops, lists, and dictionaries.

  • What you need: the Step 41 workbench (Python + VSCode). Today you’ll make two or more files, so keep the py_practice folder open.
  • Caution: today’s exercise is 100% safe. It’s nothing but creating and running files on your own computer.

Until now, we wrote code as "text read once, top to bottom." Starting today, we write it as "engineering that assembles parts." Giving a name to a frequently used bundle of code and storing it is a function, and a file organizing those functions is a module. Every print(), input(), len(), int() you’ve used so far is a function someone built in advance. Starting today, we build our own. The moment you know how to split long code into small pieces, long code stops being scary.


1. Learning Objectives

By the end of this chapter, you will be able to:

  • Define and call functions with def and return
  • Explain the roles of parameters (intake) and return values (outtake)
  • Know why variables inside a function aren’t visible outside (scope)
  • Split functions into a separate .py file (module) and bring them in with import
  • Design "if not provided, use this" options with default parameters
  • Assemble a menu-driven program with a "director function + part functions" structure

2. Background Knowledge — Today’s Tools and Concepts

Today’s Tools at a Glance

Category Details
Language/environment Python 3.12 or later, VSCode (the Step 41 workbench)
Today’s commands/grammar def name(parameters):, return, function calls name(value), default parameters, import module, from module import function
Concepts needed definition vs. call, parameters and return values, scope (local variables), modules

2-1. Functions — A Machine Where Input Goes In and Results Come Out

This is what a function looks like.

def add(a, b):
    return a + b
  • def: the declaration "I’m defining a function" (short for define)
  • add: the function’s name
  • (a, b): parameters — the intake slots that receive ingredients
  • return: the return — the outtake that sends back the result and ends the function

A definition alone does nothing. It’s merely been made. To use it, you call its name and feed in ingredients. Say add(3, 5) and 3 and 5 go into a and b, and 8 comes out. This is called a call.

2-2. Why Functions — The Curse of Copying

Without functions, you copy code when you need the same work again. But if one of five copies has a mistake, you must find and fix all five, and miss one and that night there’s an accident. With a function there’s only one original, so a fix happens in one place. There’s a saying: "if you’re writing the same code for the third time, it’s time to make it a function."

2-3. Scope — Secrets Inside the Workshop

A variable made inside a function is invisible outside. A function is an independent workshop, and the outside only receives the result pushed out through return. This is called scope, and a variable inside the workshop is called a local variable. It feels frustrating at first, but thanks to it, the variables outside the workshop never get contaminated. The bigger the program, the more this isolation is a lifeline.

2-4. Modules and import — Pulling from the Warehouse

As functions pile up, one file gets messy. So you gather related functions into a separate .py file. That file is a module, and other files pull from it with import filename. As you tried import random in Step 43’s mission, random is a module Python provides by default. Today we build our own module.


3. Follow Along

3-1. First Function — The Greeting Machine

Create a file myfunc.py.

def greet(name):
    return f"Hello, {name}!"

msg = greet("Minsu")
print(msg)
print(greet("Jiyoung"))
Hello, Minsu!
Hello, Jiyoung!

(Verified on 2026-09-09.)

How to read it: look at the two ways of calling. msg = greet(...) stores the result in a variable to use later; print(greet(...)) uses the result immediately. Since return is the outtake, there must be a receiver (a variable or print) for the result to come out into the world.

Experiment: delete the return and put print(f"Hello, {name}!") in its place, then run msg = greet("Minsu") and print(msg). The greeting prints once, followed by None (verified on 2026-09-09). That’s because a machine without an outtake returns None. This experiment is the vaccination against Wall 2.

3-2. Several Parameters — Calculation Machines

def add(a, b):
    return a + b

def sub(a, b):
    return a - b

def mul(a, b):
    return a * b

def div(a, b):
    return a / b

print(add(10, 3))
print(sub(10, 3))
print(mul(10, 3))
print(div(10, 3))
13
7
30
3.3333333333333335

(Verified on 2026-09-09.)

How to read it: parameters go in by order. In add(10, 3), 10 goes to a and 3 to b. They go in by position, not by name. Also notice that / produces a decimal. Python’s division / always returns a decimal.

3-3. Functions Without Ingredients, Functions Without Results

def show_menu():
    print("=== Calculator ===")
    print("1. Add")
    print("2. Subtract")

show_menu()
show_menu()

How to read it: if no ingredients are needed, leave the parentheses empty (def show_menu():). If no result needs returning, skip return — a "does its work and ends" function. We printed the menu twice but wrote the code once. That’s the first virtue of functions.

3-4. Scope Experiment — Inside and Outside

def make_secret():
    secret = "The password is 1234"
    print("Inside:", secret)

make_secret()
print(secret)   # causing an error on purpose

Run it (verified on 2026-09-09):

Inside: The password is 1234
Traceback (most recent call last):
  File "...\s44_scope.py", line 6, in <module>
    print(secret)
          ^^^^^^
NameError: name 'secret' is not defined

How to read it: "no such name" — outside the workshop, the inside variable doesn’t exist. Notice that the print("Inside:", ...) inside the function ran fine, and only the outside access was denied. Causing errors on purpose is the fastest way to learn scope. Most of the later "I clearly made it, so why doesn’t it know?" errors are this scope problem.

3-5. Making a Module — The Calculator Warehouse

Now split into two files. Create both files in the same folder.

The warehouse side, calc.py:

def add(a, b):
    return a + b

def sub(a, b):
    return a - b

def mul(a, b):
    return a * b

def div(a, b):
    return a / b

The using side, main.py:

import calc

print(calc.add(10, 3))
print(calc.mul(10, 3))

Run main.py.

13
30

(Verified on 2026-09-09.)

How to read it: import calc means "bring in the whole calc.py file," and calc.add means "use the add from that warehouse." The dot (.) is read as "’s," remember? You run from the using side (main.py); the warehouse (calc.py) doesn’t need running. The moment you split files, the program gains the structure of "parts warehouse + assembly line."

3-6. from import — Pulling Out Only What You Need

main2.py:

from calc import add, sub

print(add(1, 2))
print(sub(5, 3))
3
2

(Verified on 2026-09-09.)

How to read it: it means "pull only add and sub out of the calc warehouse." Brought in this way, you call it directly as add, not calc.add. Both the always-write-the-warehouse-name style (3-5) and the pull-only-what-you-need style (3-6) are widely used — being able to read both is enough.

3-7. Default Parameters — A Machine That Works Without Ingredients

A parameter can have a default value — "if not provided, use this."

def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"

print(greet("Minsu"))
print(greet("Jiyoung", "Good to see you"))
Hello, Minsu!
Good to see you, Jiyoung!

(Verified on 2026-09-09.)

How to read it: leave out the second ingredient and the default "Hello" is used; provide it and the provided value is used. It’s a design fitting the situation "mostly used this way, but occasionally differently." Later, when you build tools, you make options like "default port 80, specify if different" with this method.

3-8. Functions Calling Functions — The Assembly Line

A function can call other functions. This is the true shape of "assembly."

def ask_number(msg):
    return int(input(msg))

def add(a, b):
    return a + b

def main():
    x = ask_number("First number: ")
    y = ask_number("Second number: ")
    print("Result:", add(x, y))

main()

Example run (entering 3 and 5) — verified on 2026-09-09:

First number: 3
Second number: 5
Result: 8

How to read it: main() is the director of the assembly line. It delegates receiving numbers to ask_number, calculation to add, and only conducts the order itself. It’s the model structure where each function does one thing. Also notice that the main() call sits at the very bottom of the file — a layout where all definitions finish and the assembly line powers on last. Once a script passes a hundred lines, this "director function + part functions" structure is the only survival strategy.


4. Missions & Exercises

Mission — A Menu-Driven Calculator

Use 3-5’s calc.py as the warehouse as-is, and complete main.py per the requirements below.

  1. Show a menu:
=== Calculator ===
1. Add  2. Subtract  3. Multiply  4. Divide  0. Exit
  1. When the user enters a number and two values, call the corresponding function and print the result.
  2. The menu repeats until 0 is entered (a while + if combination).
  3. Entering a nonexistent number (e.g., 9) prints No such menu item.

Hint: put an if/elif fork that calls different functions by number inside a while. When done, test everything: 3+5, 10-4, 6*7, 8/2, an invalid number, and exit.

Exercises

Question 1. You defined a function with def and ran the file, but there was no output at all. Why, and what was missing?

Question 2. If you call a function without return, store the result in a variable, and print it, what comes out and why?

Question 3. In 3-4, explain why the print(secret) outside the function raised a NameError, using the concept of "scope."

Question 4. Explain the difference between calling with import calc then calc.add(1, 2), versus from calc import add then add(1, 2).


5. Model Answers & Completion Criteria

Mission Model Answer

calc.py is identical to 3-5. main.py:

import calc

def show_menu():
    print("=== Calculator ===")
    print("1. Add  2. Subtract  3. Multiply  4. Divide  0. Exit")

def main():
    while True:
        show_menu()
        choice = input("Pick a number: ")
        if choice == "0":
            print("Exiting.")
            break
        if choice not in ("1", "2", "3", "4"):
            print("No such menu item")
            continue
        a = int(input("First number: "))
        b = int(input("Second number: "))
        if choice == "1":
            print("Result:", calc.add(a, b))
        elif choice == "2":
            print("Result:", calc.sub(a, b))
        elif choice == "3":
            print("Result:", calc.mul(a, b))
        elif choice == "4":
            print("Result:", calc.div(a, b))

main()

Commentary: menu output goes to show_menu() and the overall flow to main() — the "director + parts" structure. choice not in ("1", "2", "3", "4") is a containment check against a tuple, filtering invalid numbers in one line. The point is returning to the menu via continue without asking for numbers.

How to verify: ① Check that each of 1–4 calls the correct function. ② Enter 9 and see that "No such menu item" prints and the menu returns. ③ See that 0 exits. ④ 8 / 2 coming out as 4.0 in division is normal — / always returns a decimal.

Exercise Answers

Answer 1. A definition (def) only makes the thing; it doesn’t run. What’s missing is the call. You must add a call line like print(add(3, 5)) near the bottom of the file. A file containing only def blocks ending quietly is normal.

Answer 2. None comes out. Without return, Python automatically returns None (verified on 2026-09-09: calling and storing a greet without return printed None). A function that returns a value needs return result at the end. print only shows on screen — it doesn’t return anything.

Answer 3. Because secret is a local variable made inside the make_secret function, its scope (valid range) is limited to the function’s interior. The moment the function ends, that variable disappears, and the outside can only receive what was pushed out via return. In verification too, the inside print was fine and only the outside access was denied with NameError: name 'secret' is not defined (verified on 2026-09-09).

Answer 4. import calc brings the whole module, so you call with the warehouse name attached, like calc.add; from calc import add pulls out a specific function, so you call it directly as add. The result is the same — only the calling shape differs. The former makes the source explicit; the latter writes shorter — each has its trade-off.

Completion Criteria Checklist

  • [ ] I can define and call functions with def and return
  • [ ] I can explain the difference between definition and call
  • [ ] I know parameters go in by position (order)
  • [ ] I verified by experiment that a function without return gives back None
  • [ ] I can explain local variables and scope together with the NameError experiment
  • [ ] I can make a module and import it both ways: import / from import
  • [ ] I completed the mission (menu-driven calculator) and tested all four paths

6. Common Pitfalls & Fixes

Wall 1. I defined a function and nothing happens

Symptom: you ran the file but there’s no output.
Cause: defining (def) and calling are different. You made it but never used it.
Fix: check for a call line (e.g., print(add(3, 5))) near the bottom of the file. A file containing only def blocks ending quietly is normal.

Wall 2. I used print instead of return and got None

Symptom: after msg = greet("Minsu"), print(msg) shows None.
Cause: a function without return automatically returns None (confirmed by testing on 2026-09-09).
Fix: first decide whether that function "shows things on screen" or "returns a value." To return a value, you need return result at the end.

Wall 3. It says the module can’t be imported

Symptom (verified on 2026-09-09):

Traceback (most recent call last):
  File "<string>", line 1, in <module>
ModuleNotFoundError: No module named 'calc_wrong'

Cause: the two files are in different folders, the file name is spelled differently, or it was saved with an extension other than .py.
Fix: check in the terminal with dir that both files are in the same folder. The filename must be exactly calc.py. Windows hides extensions, so beware the trap of saving as calc.py.txt — turn on Explorer’s "View → File name extensions" to check.

Wall 4. I edited the module but the old result comes out

Symptom: you modified calc.py but the result is unchanged.
Cause: you ran without saving the file, or an old copy exists somewhere.
Fix: save all files (Ctrl+S) and run again. In VSCode, a dot (●) on the file tab means unsaved changes.

Wall 5. Trying to split perfectly from the start, I build nothing

Symptom: your hands stop while pondering design.
Cause: design needs a result before it can be refined.
Fix: the industry’s order is "first make it work, then tidy it." Put everything in one while first and run it, then extract the menu-printing part into a function. The work of splitting and polishing running code is called refactoring.


7. Summary

Today’s Concepts

Concept One-line description
Function A named part made of a code bundle — defined with def, called by name
Parameter The intake slot receiving ingredients — goes in by position (order)
Return value The result return sends — None if absent
Scope A variable’s valid range — a function’s inner variables are invisible outside
Module A .py file gathering functions — pulled out with import
Default parameter An "if not provided, use this" option (greeting="Hello")

Today’s Grammar

Grammar What it does
def name(parameters): Define a function
return value Return a result + end the function
name(value) Call a function
def f(a, b="default"): Default parameter
import calccalc.add(...) Bring the whole module
from calc import addadd(...) Pull out only the needed function

Instincts More Important Than Grammar

Today’s three takeaways are assets that follow you no matter what language you switch to. The courage to name repeated code (functions), the eye to design the contract of ingredients and results (parameters and return), and the habit of organizing parts into warehouses (modules). Two maxims of good functions come along too. "Do one thing" — if an add function also prints, you can’t use it when you need only the calculation without output. "Let the name say the work" — add beats do_it, and show_menu beats f1.

Later in this book, you’ll gather your homemade tools into a module like mytools.py and reuse them. Working security engineers each grow such a personal module too. The more powerful a reusable tool becomes, the firmer the principle of where it’s used must be. ⚠️ All exercises in this chapter are for your own lab and legal platforms only. Applying them to unauthorized systems is a crime.


Once every box is checked, Step 44 is complete. Click the checkbox in the sidebar to save your progress.