Skip to content

6 min read

American Corners

Modular programming

Why break a program into parts

A big program is hard to think about all at once. The trick is to stop treating it as one giant block and start treating it as a set of small parts, each solving one clear job. This is what "modular" means: your program is made of modules, small pieces you can build, test, and reuse on their own.

You have already met the main tool for this. A function is a smaller part of your program that solves one specific sub-problem. In this lesson we put functions together: functions that call other functions, and import, which lets you pull in code that someone else already wrote. Think of your robot on the screen learning a few simple moves, then combining them into something bigger.

Functions that call functions

The real power of functions shows up when one function uses another. You solve a small problem once, give it a name, and then build on top of it without thinking about the details again.

Say you want to draw a house. A house is really just two shapes: a square for the walls and a triangle for the roof. So solve those two small problems first.

import turtle

t = turtle.Turtle()

def draw_square(size):
    for _ in range(4):
        t.forward(size)
        t.left(90)

def draw_triangle(size):
    for _ in range(3):
        t.forward(size)
        t.left(120)

Now the house is easy. A draw_house function does not repeat any drawing code. It just calls the two functions you already have, and moves the turtle to the top of the walls before it adds the roof.

def draw_house(size):
    draw_square(size)      # the walls
    t.penup()
    t.goto(0, size)        # move to the top-left corner
    t.setheading(0)        # face right again
    t.pendown()
    draw_triangle(size)    # the roof on top

draw_house(100)
turtle.done()

Notice what happened. draw_house reads almost like a sentence: draw the walls, go up, draw the roof. Each smaller function hides its own loop, so the bigger function stays short and clear. If you later want a street of houses, you write one more loop that calls draw_house a few times. You never touch the square or the triangle again. That is modular thinking: build small, then combine.

import: borrowing code that already exists

import is modularity on a larger scale. Instead of writing every function yourself, you bring in a whole collection of functions that comes ready-made. That is exactly what the first line of every Turtle program does.

import turtle
import random

t = turtle.Turtle()
t.forward(random.randint(50, 200))

import turtle gives you all the drawing functions. import random gives you functions for random numbers. You did not write forward or randint; someone else did, and import makes their work available to you. A collection like this is called a library or a module.

There is a shorter form you may have seen:

from turtle import *

This pulls the turtle functions straight into your file, so you can write forward(100) instead of t.forward(100).

Tip

The import turtle form is usually the clearer one. When you write turtle.forward(100), anyone reading your code can see exactly where forward comes from. With from turtle import *, that name just appears out of nowhere, which gets confusing once your program uses more than one library.

Splitting your own code into files

Once you have several useful functions, you can move them into their own file and import them the same way you import turtle. A module is nothing more than a Python file. If you save your shape functions in a file called shapes.py, another file can borrow them:

from shapes import draw_house

draw_house(100)

That is the whole idea. turtle and random are files that come with Python; shapes.py is a file you wrote. Both are modules, and import treats them the same. Keeping related functions together in their own file, with clear names, is how a growing program stays readable instead of turning into one long, tangled script.

Try this now

Build the house yourself from small parts. Write a draw_square(size) and a draw_triangle(size), each with its own for loop, then write a draw_house(size) that only calls those two (with the penup, goto, pendown move between them). Run draw_house(100). Notice that draw_house contains no drawing loop of its own, just the names of jobs you already solved.

Check yourself

  1. In the house example, draw_house never contains a for loop of its own, yet it still draws two shapes made with loops. Where did those loops go, and why does that make draw_house easier to read?
  2. What is the difference between import turtle and from turtle import *? Give one reason the first form can be clearer.
  3. You have written three drawing functions and want to use them in a new program without copying them. What would you do, and what does the word "module" mean here?

Where this lesson comes from

Built from

  • Programming in a Data World: Video 5.0 Modular Programming
  • Lecture 4 (functions) deck: draw_shape / draw_increasing_squares composition, the def template

alphaPlan courses are built from taught programmes rather than invented for the web. Where a claim rests on an outside standard or a reported case, it is named above so you can check it rather than take our word for it.

This course was developed by alphaPlan Center from programs delivered in partnership with the American Corners network.

Found something unclear, outdated or improvable? Suggest an improvement