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
mathmodule in detail - What a package is, what
__init__.pydoes, 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:
| Benefit | What it means in practice |
|---|---|
| One place to change | A rule is defined once, not copied into three files |
| Easier to find | The file name says what is inside |
| Reusable | Another program imports it instead of copying it |
| Easier to test | One module can be checked on its own |
| Shorter files | Each file holds one subject |
| Fewer name clashes | school.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:
| Form | Use it when |
|---|---|
import math | normal use — the origin stays visible |
from math import sqrt | one or two names used many times |
import math as m | the module name is long and used constantly |
from math import sqrt as root | two 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.
| Module | What it is for |
|---|---|
math | square roots, rounding, constants, trigonometry |
random | random numbers, shuffling, picking |
datetime | dates, times, differences between them |
json | reading and writing JSON text |
os and pathlib | files and folders |
csv | reading and writing spreadsheet-style files |
re | searching text with patterns |
statistics | mean, median, standard deviation |
collections | extra container types such as Counter |
itertools | tools 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.
| Function | What it does | Example | Result |
|---|---|---|---|
math.sqrt(x) | square root | math.sqrt(144) | 12.0 |
math.floor(x) | rounds down | math.floor(7.8) | 7 |
math.ceil(x) | rounds up | math.ceil(7.2) | 8 |
math.fabs(x) | distance from zero | math.fabs(-5) | 5.0 |
math.pow(x, y) | x to the power of y | math.pow(2, 3) | 8.0 |
math.factorial(x) | 1 × 2 × … × x | math.factorial(5) | 120 |
math.gcd(a, b) | greatest common divisor | math.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:
- Make a folder, named as you want the package named.
- Put an
__init__.pyinside it, even an empty one. - 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:
| Module | Package | |
|---|---|---|
| What it is | one .py file | a folder of modules |
| Example | rules.py | school/ |
| Marked by | being a .py file | holding __init__.py |
| Imported with | import rules | import school.rules |
| Can contain | variables, functions, classes | modules and other packages |
| Use when | related code fits in one file | one 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.
| Mistake | What you see | Why it happens | Fix |
|---|---|---|---|
| Importing a module Python cannot find | ModuleNotFoundError: No module named 'rules' | The file is not in any folder on sys.path | Put it beside your program, or add its folder |
| Naming your file after a standard module | Strange errors from inside the library | Your math.py is found before Python's | Rename your file |
| Top-level code in an imported module | Output appears the moment you import | Everything at the top level runs on import | Move it into main() behind the __main__ guard |
| Expecting an edit to take effect | The old value keeps coming back | A module is loaded once and cached | Restart, or importlib.reload() |
from module import * | A function quietly replaced by another | Names collide with no warning | Import the names you need |
| Two modules exporting the same name | One value silently overwrites the other | The later import wins | Import with as, or keep the module prefix |
import package and then using a submodule | AttributeError: module has no attribute | Importing a package does not import its modules | import package.module, or import it in __init__.py |
| A hyphen in a package folder name | SyntaxError: invalid syntax | A hyphen means subtraction | Use an underscore |
| Circular imports | ImportError: cannot import name ... | Two modules import each other | Move the shared part into a third module |
Forgetting __init__.py | Imports that work in one place and not another | A folder without it behaves differently | Add 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 modulekeeps the origin visible;from module import nameis shorter;asrenames;import *collides silently.sys.pathis the list of folders Python searches, andModuleNotFoundErrormeans 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,statisticsand many more. math.floor()rounds down,math.ceil()rounds up, and the built-inround()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 packagedoes 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