Code Organization

By the Syncfusion DevTutorials team · Last updated

Modules and packages: creating and importing them, __name__ and the __main__ guard, the module search path, the standard library, math, and __init__.py.

Think about a kitchen with one enormous drawer.

Spoons, knives, scissors, batteries, string, old receipts — everything in one place. It works for a week. After a month you cannot find anything, and two people cannot use it at once.

So you put knives in one drawer, spices on one shelf, and tools in a box. Nothing new was bought. The same things are just somewhere you can find them.

Programs reach that point too. A file grows past a few hundred lines and nobody can find anything in it. Python's answer is the module: one file holding code that belongs together, which other files can use.

What You Will Learn

In this lesson, you will learn:

  • What a module is, and why splitting code across files helps
  • Every way to import, and when to use each one
  • What __name__ and __main__ really mean
  • Where Python looks for a module, and what to do when it cannot find one
  • The modules Python already ships with, and the math module in detail
  • What a package is, what __init__.py does, and how a package differs from a module

By the end of this lesson, you will be able to organise code across files, reuse functionality through imports, and structure larger projects using modules and packages.

Introduction to Modules

What Is a Module?

A module is a Python file. Its name is the file name without .py, so school.py is the module school.

Anything defined in it — variables, functions, classes — can be used from another file once it is imported.

A module is a Python file whose code other files can use.

Here is a file called school.py being written, and then used from another place. The rule lives in one file, and anything that needs it can ask for it by name.

Code kept in one file, used from another

import sys, pathlib, tempfile

folder = tempfile.mkdtemp()
pathlib.Path(folder, "school.py").write_text(
    "PASS_MARK = 35\n"
    "\n"
    "def has_passed(marks):\n"
    "    return marks >= PASS_MARK\n"
)
sys.path.insert(0, folder)

import school

print(school.PASS_MARK)
print(school.has_passed(78))
print(school.has_passed(20))

In a real project you create a module by saving a file. The cells on this page cannot save to your computer, so each one writes its file into a temporary folder first and adds that folder to Python's search list. Those first few lines are the page working around the browser — the lines that matter come after import.

Why Modules Are Used

A program kept in one file has three problems, and all three get worse as it grows.

Nothing can be found. Eight hundred lines in one file means scrolling to find anything.

Nothing can be reused. A grading rule written inside a report program cannot be used by the attendance program without copying it.

Nobody can work alongside you. Two people editing one file collide constantly.

One rule, used by two different programs

import sys, pathlib, tempfile

folder = tempfile.mkdtemp()
pathlib.Path(folder, "rules.py").write_text(
    "PASS_MARK = 35\n"
    "\n"
    "def grade_for(marks):\n"
    "    if marks >= 80:\n"
    "        return 'A'\n"
    "    if marks >= 60:\n"
    "        return 'B'\n"
    "    return 'C'\n"
)
sys.path.insert(0, folder)

import rules

print("Report card uses it:", rules.grade_for(78))
print("Attendance letter uses it:", rules.grade_for(90))
print("Pass mark comes from one place:", rules.PASS_MARK)

Change the A boundary inside rules.py and both programs follow. That is the whole point.

Benefits of Modular Programming

Splitting code into modules pays off in ways you will notice every day:

BenefitWhat it means in practice
One place to changeA rule is defined once, not copied into three files
Easier to findThe file name says what is inside
ReusableAnother program imports it instead of copying it
Easier to testOne module can be checked on its own
Shorter filesEach file holds one subject
Fewer name clashesschool.marks and sports.marks can both exist

Creating a Module

Creating a module is just saving a file. There is no special syntax and nothing to declare.

On your own computer you would save this as school.py:

"""Rules the school uses for marks and grades."""

PASS_MARK = 35
MAX_MARKS = 100

def has_passed(marks):
    """Return True when the marks reach the pass mark."""
    return marks >= PASS_MARK

def percentage(marks):
    """Return the marks as a percentage of the maximum."""
    return marks / MAX_MARKS * 100

That is a module. It has a docstring at the top saying what it is for, two constants, and two functions.

Here is the same file, written and used from a cell:

The module above, created and used

import sys, pathlib, tempfile

folder = tempfile.mkdtemp()
pathlib.Path(folder, "school_full.py").write_text(
    '"""Rules the school uses for marks and grades."""\n'
    "\n"
    "PASS_MARK = 35\n"
    "MAX_MARKS = 100\n"
    "\n"
    "def has_passed(marks):\n"
    '    """Return True when the marks reach the pass mark."""\n'
    "    return marks >= PASS_MARK\n"
    "\n"
    "def percentage(marks):\n"
    '    """Return the marks as a percentage of the maximum."""\n'
    "    return marks / MAX_MARKS * 100\n"
)
sys.path.insert(0, folder)

import school_full

print(school_full.__doc__)
print(school_full.has_passed(78))
print(school_full.percentage(78))

Importing Modules

import school runs the file once and gives you the name school to reach its contents through.

Two things are worth knowing about that first run.

The file runs top to bottom when it is first imported. Anything at the top level happens there and then.

The file runs when it is imported

import sys, pathlib, tempfile

folder = tempfile.mkdtemp()
pathlib.Path(folder, "noisy.py").write_text(
    'print("noisy.py is being set up")\n'
    "VALUE = 42\n"
)
sys.path.insert(0, folder)

print("before the import")
import noisy
print("after the import:", noisy.VALUE)

It runs only once, however many times you import it.

A second import does not run the file again

import sys, pathlib, tempfile

folder = tempfile.mkdtemp()
pathlib.Path(folder, "once.py").write_text('print("setting up once.py")\nVALUE = 1\n')
sys.path.insert(0, folder)

import once
import once
import once

print("imported three times, printed once")

Python keeps every imported module in sys.modules and hands back the one it already has.

__name__

Every module has a variable called __name__. Python sets it, and its value answers one question: was this file started directly, or imported by something else?

  • Started directly: __name__ is "__main__"
  • Imported: __name__ is the module's own name

The same file sees two different names

import sys, pathlib, tempfile

folder = tempfile.mkdtemp()
pathlib.Path(folder, "greet.py").write_text(
    'print("inside greet.py, __name__ is", __name__)\n'
)
sys.path.insert(0, folder)

print("in this cell, __name__ is", __name__)
import greet

This cell is the program being run, so its __name__ is "__main__". The imported file saw "greet" instead.

__main__

"__main__" is the name Python gives to whatever was started directly.

That gives a file a way to tell the difference, which is where this very common line comes from:

if __name__ == "__main__":
    main()

Read it as: only do this when I am the program being run, not when somebody imports me.

Running a Module as a Script

Here is why that matters. A module that prints or asks questions at the top level will do so the moment anyone imports it.

A file that misbehaves when imported

import sys, pathlib, tempfile

folder = tempfile.mkdtemp()
pathlib.Path(folder, "report_bad.py").write_text(
    "def grade_for(marks):\n"
    "    return 'A' if marks >= 80 else 'B'\n"
    "\n"
    'print("REPORT CARD")\n'
    'print("Grade:", grade_for(90))\n'
)
sys.path.insert(0, folder)

print("I only wanted the function:")
import report_bad
print("...but the whole report printed itself")

Guarding it fixes the problem, and the file still works when run directly:

The guard, and what each way of starting it does

import sys, pathlib, tempfile, runpy

folder = tempfile.mkdtemp()
pathlib.Path(folder, "report_good.py").write_text(
    "def grade_for(marks):\n"
    "    return 'A' if marks >= 80 else 'B'\n"
    "\n"
    "def main():\n"
    '    print("REPORT CARD")\n'
    '    print("Grade:", grade_for(90))\n'
    "\n"
    'if __name__ == "__main__":\n'
    "    main()\n"
)
sys.path.insert(0, folder)

print("--- importing it ---")
import report_good
print("nothing printed, and grade_for is ready:", report_good.grade_for(70))

print("--- running it as a script ---")
runpy.run_module("report_good", run_name="__main__")

On your own computer the second half is simply this at a terminal:

python report_good.py

runpy.run_module is used here only because a browser has no terminal. It starts the module with __name__ set to "__main__", which is exactly what the terminal command does.

Module Entry Point

The main() function plus the if __name__ == "__main__": guard is called the entry point. It is the standard shape for any file meant to be both run and imported.

def main():
    ...

if __name__ == "__main__":
    main()

Put the work in main() rather than directly under the guard. Then the file can be run, imported, and tested, and main() can even be called by another program.

Code written at the top level of a module runs on import. Anything that prints, asks for input, reads a file or connects to something belongs inside a function, behind the __main__ guard.

Now that you know what a module is and how Python runs one, the next step is learning the different ways to import and use modules in your own programs.

Working with Modules

The import Statement

import module brings in the whole module. Everything inside is reached through the module name.

Reaching things through the module name

import math

print(math.sqrt(144))
print(math.pi)

The module name stays in front, which makes it obvious where sqrt came from. That is a feature, not extra typing.

The from ... import Statement

from module import name brings one name straight into your file, with no prefix.

Bringing one name in directly

from math import sqrt, pi

print(sqrt(144))
print(pi)

Shorter to use, but the origin is no longer visible at the point of use. In a long file, sqrt(x) leaves the reader hunting for where it came from.

There is also from module import *, which brings in everything:

Why the star import causes trouble

from math import *

print(sqrt(144))

def pow(a, b):
    return "my own pow"

print(pow(2, 3))

math has its own pow, and the definition here replaced it. In a longer file the collision goes the other way just as easily, and nothing warns you. Avoid import * outside quick experiments.

Import Aliases (as)

as gives an imported name a different name in your file.

Renaming on the way in

import math as m
from math import sqrt as square_root

print(m.pi)
print(square_root(144))

Two reasons to use it: a long module name used constantly, and two modules that both export the same name.

Two names that would otherwise clash

import sys, pathlib, tempfile

folder = pathlib.Path(tempfile.mkdtemp())
(folder / "school_rules.py").write_text("PASS_MARK = 35\n")
(folder / "sports_rules.py").write_text("PASS_MARK = 50\n")
sys.path.insert(0, str(folder))

from school_rules import PASS_MARK as school_pass
from sports_rules import PASS_MARK as sports_pass

print("School:", school_pass)
print("Sports:", sports_pass)

Without the aliases the second import would silently overwrite the first, and the school pass mark would quietly become 50.

Importing Specific Members

Import what you use, not everything that exists.

Taking only what is needed

import sys, pathlib, tempfile

folder = tempfile.mkdtemp()
pathlib.Path(folder, "marking.py").write_text(
    "PASS_MARK = 35\n"
    "MAX_MARKS = 100\n"
    "\n"
    "def has_passed(marks):\n"
    "    return marks >= PASS_MARK\n"
    "\n"
    "def percentage(marks):\n"
    "    return marks / MAX_MARKS * 100\n"
)
sys.path.insert(0, folder)

from marking import has_passed, PASS_MARK

print(has_passed(78))
print(PASS_MARK)

dir() shows what a module offers, which is how you find out what there is to import:

Looking inside a module

import math

names = [name for name in dir(math) if not name.startswith("_")]

print("How many public names:", len(names))
print(names[:10])

Here is the whole set of import forms in one place, with when to reach for each:

FormUse it when
import mathnormal use — the origin stays visible
from math import sqrtone or two names used many times
import math as mthe module name is long and used constantly
from math import sqrt as roottwo modules export the same name
from math import *almost never — names collide silently

Module Search Path

When you write import school, Python looks through a list of folders in order and takes the first match. That list is sys.path.

Where Python looks

import sys

print("Number of folders searched:", len(sys.path))
for folder in sys.path[:4]:
    print(folder)

The first entry is the folder of the program being run, which is why a module sitting beside your file is found without any setup.

If nothing matches, Python stops:

A module that does not exist

import school_rules_v2

ModuleNotFoundError means Python looked through every folder in sys.path and found nothing with that name. The usual causes are a typo, the file being in a different folder, or the package not being installed.

Adding a folder to sys.path is what every cell on this page does:

Adding a folder to the search list

import sys, pathlib, tempfile

folder = tempfile.mkdtemp()
pathlib.Path(folder, "late.py").write_text("MESSAGE = 'found me'\n")

print("Before adding the folder, the import would fail.")

sys.path.insert(0, folder)
import late

print(late.MESSAGE)

In a real project you rarely touch sys.path. Keeping files in sensible folders, and installing packages properly, is the normal way.

Reloading Modules

A module is loaded once. Editing the file afterwards changes nothing until the program restarts — or until you reload it on purpose.

Editing the file is not enough

import sys, pathlib, tempfile, importlib

folder = tempfile.mkdtemp()
rules_file = pathlib.Path(folder, "live_rules.py")
rules_file.write_text("PASS_MARK = 35\n")
sys.path.insert(0, folder)

import live_rules
print("First import:", live_rules.PASS_MARK)

rules_file.write_text("PASS_MARK = 40\n")

import live_rules
print("Imported again after the edit:", live_rules.PASS_MARK)

importlib.reload(live_rules)
print("After importlib.reload:", live_rules.PASS_MARK)

The second import did nothing, because Python already had the module. Only importlib.reload() read the file again.

Reloading is for experimenting at an interactive prompt. In a normal program, restart instead — reload leaves older objects pointing at the previous version, which gets confusing quickly.

So far, every module has been one you created yourself. Python also comes with hundreds of ready-made modules that you can use immediately.

Built-in Modules

What Are Built-in Modules?

A built-in module comes with Python. There is nothing to install; you import it and use it.

Four modules that are already there

import math
import random
import datetime
import json

print(math.sqrt(81))

random.seed(7)
print(random.randint(1, 6))

print(datetime.date(2026, 3, 14).strftime("%d %B %Y"))
print(json.dumps({"student": "Asha", "marks": 78}))

Nothing was downloaded. All four ship with Python.

Standard Library Overview

The whole collection is called the standard library. It is large, and knowing roughly what is in it saves you writing code somebody has already written.

ModuleWhat it is for
mathsquare roots, rounding, constants, trigonometry
randomrandom numbers, shuffling, picking
datetimedates, times, differences between them
jsonreading and writing JSON text
os and pathlibfiles and folders
csvreading and writing spreadsheet-style files
researching text with patterns
statisticsmean, median, standard deviation
collectionsextra container types such as Counter
itertoolstools for looping and combining

Two of them doing real work

import statistics
from collections import Counter

marks = [78, 85, 60, 78, 92, 78]

print("Mean:", round(statistics.mean(marks), 1))
print("Median:", statistics.median(marks))
print("Most common mark:", Counter(marks).most_common(1))

Exploring Module Contents

Three tools tell you what a module holds, without leaving Python.

Finding out what is inside

import statistics

print([n for n in dir(statistics) if not n.startswith("_")][:8])

print(statistics.median.__doc__.splitlines()[0])

print(statistics.__name__)

dir() lists the names. __doc__ gives the docstring. help() prints a full description, and is worth using at an interactive prompt — it prints a lot, so it is not used in these cells.

Math Module

Importing the math Module

The math module is the most used built-in of all, so this section looks at it closely. It is imported like any other module.

Importing math

import math

print(math.sqrt(144))
print(math.floor(7.8))
print(math.ceil(7.2))

Mathematical Constants

math also provides fixed values that come up again and again. Two of them, pi and inf, are shown below along with the rest.

The constants math provides

import math

print("pi:", math.pi)
print("e:", math.e)
print("tau:", math.tau)
print("infinity:", math.inf)
print("not a number:", math.nan)

math.pi is more accurate than anything you would type by hand, and it says what it is.

Using pi for something real

import math

radius = 7

print("Area:", round(math.pi * radius ** 2, 2))
print("Circumference:", round(2 * math.pi * radius, 2))

Mathematical Functions

The functions below are the ones you will use most. Each row shows what the function does and what it returns.

FunctionWhat it doesExampleResult
math.sqrt(x)square rootmath.sqrt(144)12.0
math.floor(x)rounds downmath.floor(7.8)7
math.ceil(x)rounds upmath.ceil(7.2)8
math.fabs(x)distance from zeromath.fabs(-5)5.0
math.pow(x, y)x to the power of ymath.pow(2, 3)8.0
math.factorial(x)1 × 2 × … × xmath.factorial(5)120
math.gcd(a, b)greatest common divisormath.gcd(12, 18)6

Rounding, three different ways

import math

value = 7.5

print("floor:", math.floor(value))
print("ceil:", math.ceil(value))
print("round:", round(value))
print("round of 8.5:", round(8.5))

round() is a built-in, not part of math, and it does something the other two do not: on an exact half it rounds to the even number. That is why round(7.5) is 8 and round(8.5) is also 8. It keeps a long column of numbers from drifting upwards.

sqrt against the ** operator

import math

print(math.sqrt(144))
print(144 ** 0.5)
print(math.sqrt(144) == 144 ** 0.5)

Both give 12.0. math.sqrt says what it means; ** 0.5 needs the reader to work it out.

A square root with no answer

import math

print(math.sqrt(-9))

ValueError: math domain error means the number was outside what the function accepts. No real number multiplied by itself gives -9.

A single module works well for small amounts of code. As projects grow, even modules start to multiply, which is where packages become useful.

Introduction to Packages

What Is a Package?

A package is a folder of modules.

A module groups code into a file. A package groups files into a folder. The idea is the same, one level up.

Why Packages Are Used

Once a project has twenty modules, a flat folder is the kitchen drawer again. A package sorts them by subject.

school/
    __init__.py
    rules.py
    report.py
    attendance.py

Now school.rules and school.report are clearly related, and the folder name gives them a shared home.

Package Structure

Packages can also hold other packages, so the nesting goes as deep as you need.

myproject/
    main.py
    school/
        __init__.py
        rules.py
        report.py
        marks/
            __init__.py
            maths.py
            science.py

Packages can hold packages. school.marks.maths is read left to right as folder, folder, file.

__init__.py

__init__.py is a file that marks a folder as a package. It runs when the package is first imported, and it is usually empty, or used to decide what the package offers.

A package built and imported

import sys, pathlib, tempfile

root = pathlib.Path(tempfile.mkdtemp())
package = root / "schoolpack"
package.mkdir()

(package / "__init__.py").write_text("")
(package / "rules.py").write_text(
    "PASS_MARK = 35\n"
    "\n"
    "def has_passed(marks):\n"
    "    return marks >= PASS_MARK\n"
)
(package / "report.py").write_text(
    "def heading(student):\n"
    "    return 'REPORT CARD - ' + student\n"
)
sys.path.insert(0, str(root))

from schoolpack import rules, report

print(rules.has_passed(78))
print(report.heading("Asha"))

The __init__.py was empty, and the package worked. Its other job is to pull names up to the top level, so callers do not need to know which file holds what:

__init__.py deciding what the package offers

import sys, pathlib, tempfile

root = pathlib.Path(tempfile.mkdtemp())
package = root / "school2"
package.mkdir()

(package / "__init__.py").write_text(
    '"""Everything the school program needs."""\n'
    "from .rules import has_passed, PASS_MARK\n"
)
(package / "rules.py").write_text(
    "PASS_MARK = 35\n"
    "\n"
    "def has_passed(marks):\n"
    "    return marks >= PASS_MARK\n"
)
sys.path.insert(0, str(root))

import school2

print(school2.__doc__)
print(school2.has_passed(78))
print(school2.PASS_MARK)

has_passed really lives in school2/rules.py, but callers write school2.has_passed. The dot in from .rules import means "from this same package", and it is called a relative import.

Since Python 3.3 a folder without __init__.py can still be imported, as a namespace package. Include the file anyway: it is what readers expect, and it is the only place to put package-level setup.

Creating Packages

Three steps, and none of them need anything installed:

  1. Make a folder, named as you want the package named.
  2. Put an __init__.py inside it, even an empty one.
  3. Put your modules in the folder beside it.

Package names follow the same rules as variable names: small letters, no spaces, no hyphens. school_rules is fine; school-rules is not, because a hyphen means subtraction.

Importing Packages

Every form of import works with a package. The dots walk down the folders.

Four ways to reach the same function

import sys, pathlib, tempfile

root = pathlib.Path(tempfile.mkdtemp())
package = root / "school3"
package.mkdir()
(package / "__init__.py").write_text("")
(package / "rules.py").write_text("def has_passed(marks):\n    return marks >= 35\n")
sys.path.insert(0, str(root))

import school3.rules
from school3 import rules
from school3.rules import has_passed
from school3.rules import has_passed as passed

print(school3.rules.has_passed(78))
print(rules.has_passed(78))
print(has_passed(78))
print(passed(78))

One trap: import school3 on its own does not bring in the modules inside it, unless its __init__.py says so.

Importing the package is not importing its modules

import sys, pathlib, tempfile

root = pathlib.Path(tempfile.mkdtemp())
package = root / "school4"
package.mkdir()
(package / "__init__.py").write_text("")
(package / "rules.py").write_text("def has_passed(marks):\n    return marks >= 35\n")
sys.path.insert(0, str(root))

import school4

print(school4.rules.has_passed(78))

AttributeError: module 'school4' has no attribute 'rules'. The fix is import school4.rules, or a line in __init__.py that imports it.

Modules vs Packages

Here are the two ideas side by side:

ModulePackage
What it isone .py filea folder of modules
Examplerules.pyschool/
Marked bybeing a .py fileholding __init__.py
Imported withimport rulesimport school.rules
Can containvariables, functions, classesmodules and other packages
Use whenrelated code fits in one fileone file is no longer enough

A package is also a module, as far as Python is concerned — it has a __name__, a docstring, and a dot to reach inside it. The difference is what it holds.

A package is a module that holds modules

import math
import sys, pathlib, tempfile

root = pathlib.Path(tempfile.mkdtemp())
package = root / "school5"
package.mkdir()
(package / "__init__.py").write_text("")
sys.path.insert(0, str(root))

import school5

print("math is a module:", type(math).__name__)
print("school5 is also a module:", type(school5).__name__)
print("but it has a __path__:", hasattr(school5, "__path__"))
print("math does not:", hasattr(math, "__path__"))

__path__ is the folder a package was loaded from. A plain module has no folder, so it has no __path__ — that one attribute is the real difference.

Start with one file. Split it into modules when it gets hard to find things. Group modules into a package when there are too many files to scan. Do not start with the folder structure.

Common mistakes

Each row is one mistake. The middle column is what you will actually see on screen.

MistakeWhat you seeWhy it happensFix
Importing a module Python cannot findModuleNotFoundError: No module named 'rules'The file is not in any folder on sys.pathPut it beside your program, or add its folder
Naming your file after a standard moduleStrange errors from inside the libraryYour math.py is found before Python'sRename your file
Top-level code in an imported moduleOutput appears the moment you importEverything at the top level runs on importMove it into main() behind the __main__ guard
Expecting an edit to take effectThe old value keeps coming backA module is loaded once and cachedRestart, or importlib.reload()
from module import *A function quietly replaced by anotherNames collide with no warningImport the names you need
Two modules exporting the same nameOne value silently overwrites the otherThe later import winsImport with as, or keep the module prefix
import package and then using a submoduleAttributeError: module has no attributeImporting a package does not import its modulesimport package.module, or import it in __init__.py
A hyphen in a package folder nameSyntaxError: invalid syntaxA hyphen means subtractionUse an underscore
Circular importsImportError: cannot import name ...Two modules import each otherMove the shared part into a third module
Forgetting __init__.pyImports that work in one place and not anotherA folder without it behaves differentlyAdd it, even empty

What to Remember

  • A module is a Python file whose code other files can use. Its name is the file name without .py.
  • You create one by saving a file. There is nothing to declare.
  • Importing runs the whole file, once. Later imports hand back what Python already has.
  • __name__ is "__main__" in the file that was started directly, and the module's own name everywhere else.
  • if __name__ == "__main__": main() is how one file can be both imported and run.
  • import module keeps the origin visible; from module import name is shorter; as renames; import * collides silently.
  • sys.path is the list of folders Python searches, and ModuleNotFoundError means nothing in it matched.
  • Editing a file changes nothing until you restart or call importlib.reload().
  • The standard library ships with Python — math, random, datetime, json, statistics and many more.
  • math.floor() rounds down, math.ceil() rounds up, and the built-in round() goes to the nearest even number on an exact half.
  • A package is a folder of modules, marked by __init__.py, which runs on first import and can decide what the package offers.
  • import package does not import the modules inside it.
  • Start with one file. Split into modules when finding things gets hard, and into a package when there are too many files.

Next: Arrays