# Code Organization

Course: [Python](https://www.devtutorials.ai/learn/python) · Section: Modules and Packages
Source: https://www.devtutorials.ai/learn/python/modules-and-packages/code-organization

> 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.

```python runnable title="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.

```python runnable title="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:

<div className="tscroll">

| 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 |

</div>

### 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`:

```python
"""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:

```python runnable title="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.

```python runnable title="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.**

```python runnable title="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

```python runnable title="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:

```python
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.

```python runnable title="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:

```python runnable title="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:

```text
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.

```python
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.

```python runnable title="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.

```python runnable title="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:

```python runnable title="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.

```python runnable title="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.

```python runnable title="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.

```python runnable title="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:

```python runnable title="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:

<div className="tscroll">

| 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 |

</div>

### 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`.

```python runnable title="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:

```python runnable expect-error title="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:

```python runnable title="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.

```python runnable title="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.

```python runnable title="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.

<div className="tscroll">

| 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 |

</div>

```python runnable title="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.

```python runnable title="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.

```python runnable title="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.

```python runnable title="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.

```python runnable title="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.

<div className="tscroll">

| 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` |

</div>

```python runnable title="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.

```python runnable title="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.

```python runnable expect-error title="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.

```text
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.

```text
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.

```python runnable title="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:

```python runnable title="__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.

```python runnable title="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.

```python runnable expect-error title="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:

<div className="tscroll">

| | 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 |

</div>

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.

```python runnable title="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.

<div className="tscroll">

| 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 |

</div>

## 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**
