# Modules and Packages: Importing and Namespaces

> Chapter 11 of the Courseiva PYTHON-PCAP curriculum — https://courseiva.com/learn/python-pcap/modules-and-packages-101

**Official objective:** 4.1 — Create modules, import using various methods, and understand package structure with __init__.py.

## Introduction

As you write Python programs, you will quickly find yourself re-using the same pieces of code – functions for calculating dates, reading files, or handling text. Instead of writing that code from scratch each time, you can store it in a file called a module and then 'import' it into your current program. Understanding how to organise and import these modules is a core skill for the PCAP-31-03 exam, because it tests how you manage code structure and avoid naming conflicts.

## The Office Filing Cabinet Analogy

A large office with a central filing cabinet. The cabinet has many drawers, and each drawer is labelled for a different department: Sales, Marketing, IT, HR. Inside each drawer are folders, and inside each folder are individual documents.

A new employee, Alex, needs a specific sales report from the Sales department. Instead of walking to the Sales desk and asking them to hand over a copy (which could cause delays if they are busy, or confusion if they hand over the wrong report), Alex follows the office procedure. Alex opens the central filing cabinet drawer for Sales, finds the folder labelled 'Monthly Reports', and takes the specific document from that folder. Importantly, Alex does not dump all the Sales documents onto their own desk. They only take what they need. The office also has a rule: if a folder has a special yellow tag on it (like a `__init__.py` file), Alex knows that folder is a 'special department bundle' and contains extra instructions for how to access the files inside.

This is exactly how Python modules and packages work. The filing cabinet is the Python library. The drawer is a package (Sales). The folder is a sub-package (Monthly Reports). The document is a module (the sales report code). The office procedure is the `import` statement. The special yellow tag is the `__init__.py` file, which tells Python, 'This folder is a package, not just a random collection of files'. Alex's own desk is the current namespace – they only bring in the exact items they need, without cluttering their workspace.

## Core explanation

In Python, a module is simply a single file that ends with `.py`. You can think of it as a recipe book – one file containing a collection of related functions, variables, and classes. For example, you might create a file called `weather_utils.py` that contains a function called `get_temperature(city)`. To use that function in another program, you use the `import` statement. The simplest form is `import weather_utils`. When Python runs this line, it does two things: it runs all the code inside `weather_utils.py`, and it creates a new namespace (a container for names) called `weather_utils`. To call your function, you must then write `weather_utils.get_temperature("London")`. This is called 'qualifying' the name – you are explicitly telling Python to look inside the `weather_utils` namespace for the function. The benefit is that if you also have a function called `get_temperature` in your main program, they will not clash, because they live in different namespaces.

But what if you want to bring the function directly into your current namespace so you can call it as just `get_temperature("London")`? You use a different form: `from weather_utils import get_temperature`. This copies the function reference into your current namespace. The downside is that if you already have a variable named `get_temperature` in your main program, it will be overwritten. For PCAP-31-03, you must memorise the three main import forms: `import modulename`, `from modulename import item`, and `from modulename import *`. The asterisk (`*`) imports all names that the module defines at the top level, which is generally discouraged because it pollutes your namespace and can cause accidental name collisions.

Now, as you accumulate many modules, you will want to organise them into folders. A folder containing multiple `.py` files is called a package. For Python to recognise a folder as a package, the folder must contain a file named `__init__.py`. This file can be empty, but it signals to Python that the folder is a package that can be imported. For example, let us create a folder called `shapes`. Inside it, we have `circle.py` (with a function `area(radius)`) and `square.py` (with a function `area(side)`). Inside the `shapes` folder, we create an empty file named `__init__.py`. Now, from another script, we can write:

- `import shapes.circle` and then call `shapes.circle.area(5)`
- Or `from shapes.circle import area` and then call `area(5)` directly.

The `__init__.py` file can also contain code that runs when the package is first imported. For instance, you might use it to set up shared variables or to automatically import certain sub-modules so they are available immediately: `from . import circle` inside `__init__.py` would make `circle` available as `shapes.circle` without an extra explicit import.

Python has a built-in search path for modules, called `sys.path`. When you write `import weather_utils`, Python looks for a file named `weather_utils.py` in a list of directories. The first place it looks is the directory containing the script that was run (the current working directory). Then it checks every directory listed in the `PYTHONPATH` environment variable, and finally it checks standard library directories and site-packages (where third-party modules are installed). If Python cannot find the module, it raises a `ModuleNotFoundError`. This is a common exam trap – they might ask what happens when you try to import a module that is not in any of these locations.

Namespaces are crucial to understand. Every time you import a module, Python creates a new namespace object. When you assign a variable inside a module, that variable lives in the module's namespace. When you import using `from modulename import item`, the item is copied into your current namespace (the global namespace of your script). The original item in the module’s namespace remains unchanged. If you change the value of the imported item in your script, you are only changing your local copy – not the original in the module. However, if you import the module itself (`import modulename`) and then modify an attribute of that module (e.g., `modulename.some_variable = 10`), that change is visible to any other code that also imports the same module and reads that attribute. This behaviour is tested in the exam.

Finally, the `__name__` variable. When you run a Python script directly, Python sets the special variable `__name__` to `"__main__"`. When a module is imported from another script, `__name__` is set to the module's name (e.g., `"weather_utils"`). This lets you write code that only runs when the file is executed directly, not when it is imported, using the pattern `if __name__ == "__main__":`. The exam often asks about this.

To summarise the hierarchy: a package is a folder with `__init__.py`. A sub-package is a sub-folder also containing `__init__.py`. A module is a `.py` file inside any package. A function or class is defined inside a module. Each level lives in its own namespace.

## Real-world context

Imagine you are a junior developer at a company that builds a web application for managing customer orders. The application is large, with hundreds of Python files. Without modules and packages, you would have a single massive script with tens of thousands of lines, making it impossible to maintain or collaborate. Your senior developer asks you to organise the code.

First, you create a top-level package called `order_system`. Inside it, you create an `__init__.py` file (just an empty file for now). Then you create sub-packages: `order_system.models` (for database-related code), `order_system.views` (for user interface logic), and `order_system.utils` (for helper functions like date parsing and email sending). Each sub-package also gets its own `__init__.py` file.

Inside `order_system.models`, you have a module called `customer.py` containing a `Customer` class. Inside `order_system.views`, you have `order_view.py` which needs to use the `Customer` class. So you write in `order_view.py`: `from order_system.models.customer import Customer`. But there is a catch: the project is deployed on multiple servers, and the developers need to ensure that the same structure works across all environments. You decide to make the `__init__.py` file in `order_system.models` import the `Customer` class directly, so that other parts of the application can simply write `from order_system.models import Customer`. This simplifies the import paths. Your `__init__.py` looks like:

```python
from .customer import Customer
```

Now, a bug emerges: the `Customer` class needs a helper function called `validate_email` that lives in `order_system.utils`. You import it inside `customer.py` using `from order_system.utils import validate_email`. This creates a circular import risk if `utils` also tries to import from `models`. To avoid this, you restructure: you move `validate_email` into its own small module `order_system.utils.email_utils.py` and import it only inside the specific functions that need it, not at the top level of `customer.py`.

In your day-to-day work, you frequently need to test individual modules. You use the `if __name__ == "__main__":` pattern to write quick test code inside each module. For example, inside `customer.py`, you add:

```python
if __name__ == "__main__":
    c = Customer("Alice", "alice@example.com")
    print(c)
```

This lets you run `python order_system/models/customer.py` directly to test that specific module, without affecting the rest of the system when it is imported normally.

When the project grows, you encounter name clashes. Another library you use, `analytics_toolkit`, also has a `Customer` class. To avoid conflict, you use the fully qualified import: `from order_system.models.customer import Customer as OrderCustomer` and `from analytics_toolkit import Customer as AnalyticsCustomer`. This 'rename on import' (using `as`) is a practical technique the exam expects you to know.

Finally, when deploying to production, you must ensure all `__init__.py` files exist, even if empty. New developers often forget this, leading to `ModuleNotFoundError`. As a professional, you run a linter or a pre-commit hook to check that every package directory contains `__init__.py`.

## Exam focus

The PCAP-31-03 exam objective 4.1 specifically tests your ability to create modules and packages, import using various methods, and understand package structure with `__init__.py`. Here is exactly what you need to know.

First, you must memorise the three primary import statement forms:

- `import modulename`
- `from modulename import item`
- `from modulename import *`

You must also know that `import modulename as alias` and `from modulename import item as alias` are valid and commonly used. The exam will show you code snippets and ask what happens when they are executed. Common trap: they give you a module that defines a global variable, and then they import it two different ways, and ask whether changing the variable in the main script affects the module's original value. The answer is: if you do `import modulename` and then `modulename.x = 5`, the module's `x` changes globally across all importers. If you do `from modulename import x` and then `x = 5`, you are creating a new local variable `x` that shadows the imported one, and the module's original `x` remains unchanged.

Second, the `__init__.py` file. The exam loves to ask what makes a directory a package. The answer: a directory must contain an `__init__.py` file (or, in Python 3.3+, it can be a namespace package without it, but for PCAP-31-03, assume the classic behaviour that `__init__.py` is required). They will ask what happens if you try to import a module from a directory that lacks `__init__.py`. Answer: it still might work if the module is directly in `sys.path` (like a single `.py` file), but if the directory is meant to be a sub-package, the import will fail with `ModuleNotFoundError`. They may also ask about the role of `__init__.py` – it is executed when the package is first imported, and can be used to initialise package-level variables or automatically import sub-modules.

Third, the search path (`sys.path`). The exam tests your understanding of where Python looks for modules. Be able to list the order: 1) the directory containing the script, 2) any directories listed in `PYTHONPATH` environment variable, 3) standard library directories, 4) site-packages. A typical question: 'If you have two files with the same name in two different directories in `sys.path`, which one is imported?' The answer: the first one found.

Fourth, the `__name__` variable. You must know that `__name__` is set to `"__main__"` when a file is run directly, and to the module's name when imported. Questions ask what code inside an `if __name__ == "__main__":` block will do – it only runs during direct execution, not during import.

Fifth, package structure: you need to understand relative imports (using dots). For example, inside a package `shapes`, a module `shapes/circle.py` can import a sibling module `shapes/square.py` using `from . import square` (single dot for same package) or `from .square import area` (for a specific item). Double dots (`..`) go to the parent package. The exam tests that relative imports only work inside a package (i.e., they are used from within a module that is part of a package, not from a top-level script).

Specific trap patterns include:

- Importing a module that itself imports something that causes a circular import (two modules importing each other). Python will raise an `ImportError` at runtime if a circular import is detected during the import process.
- Forgetting to include `__init__.py` in a sub-package, then trying to import a module from that sub-package.
- Using `from module import *` when the module does not define `__all__`. In that case, all names not beginning with an underscore are imported. If `__all__` is defined, only those names in the list are imported.
- Thinking that `from module import *` imports all sub-modules – it does not, only top-level names from that module's namespace.

Key definitions to memorise:

- Module: a single `.py` file.
- Package: a directory containing `__init__.py` and zero or more modules or sub-packages.
- Namespace: a mapping from names to objects; each module has its own namespace.
- `__all__`: a list of strings defining the public objects when using `from module import *`.
- `sys.path`: the list of directories Python searches for modules.

Finally, be prepared for multi-select questions where you choose all valid import statements. They might mix correct syntax with incorrect ones like `import module.submodule` (valid) versus `import module.submodule.attribute` (invalid – you can only import modules or packages, not attributes, with the simple `import` statement). Use `from module import attribute` for attributes.

## Step by step

1. **Create the module file** — Create a new text file with the `.py` extension, for example `my_functions.py`. Inside, write your Python code – functions, classes, variables. This file is now a module. It must be in a directory that Python can find (either the same directory as your main script, or a directory listed in `sys.path`).
2. **Create the package folder** — Create a new directory, for example `my_package`. Inside this directory, create an empty file named `__init__.py`. This file tells Python that this directory is a package. Without it, Python will not treat the folder as a package and you will not be able to use dot notation to import modules inside it.
3. **Add sub-modules to the package** — Place additional `.py` files inside the `my_package` directory, such as `module_a.py` and `module_b.py`. These are now sub-modules of the package `my_package`. You can also create sub-packages by adding sub-directories, each containing their own `__init__.py` file.
4. **Write the import statement in your main script** — In your main script, use `import my_package.module_a` to access `module_a` via the qualified name `my_package.module_a`. Alternatively, use `from my_package.module_a import my_function` to bring `my_function` directly into your current namespace. If the module is not in `sys.path`, you will get a `ModuleNotFoundError`.
5. **Handle namespace conflicts with aliasing** — If two imported modules have functions with the same name, use the `as` keyword to rename them: `from my_package.module_a import my_function as func_a` and `from other_module import my_function as func_b`. This keeps your namespace clean and avoids accidental overwrites.
6. **Prevent execution of test code on import** — Inside your module file, any code not inside a function or class runs immediately when the module is imported. To prevent test code from running on import, wrap it inside `if __name__ == "__main__":`. This way, the test code only runs when you execute the module file directly, not when it is imported elsewhere.
7. **Optimise package initialisation in __init__.py** — Inside `__init__.py`, you can import specific sub-modules so that they become available directly as attributes of the package. For example, writing `from . import module_a` in `__init__.py` allows you to later `import my_package` and then access `my_package.module_a` directly, without an explicit `import my_package.module_a`.

## Comparisons

### import modulename vs from modulename import item

**import modulename:**
- Creates a separate namespace for the module
- Requires prefix to access items: modulename.item
- Safer for avoiding name collisions

**from modulename import item:**
- Copies the item into the current namespace
- No prefix needed when using the item
- Higher risk of overwriting existing names in current namespace

### Module (single .py file) vs Package (directory with __init__.py)

**Module (single .py file):**
- A single file that can be imported directly
- No special marker file needed
- Cannot contain sub-modules within itself

**Package (directory with __init__.py):**
- A directory that can contain multiple modules and sub-packages
- Requires __init__.py to signal it is a package
- Allows hierarchical organisation with dot notation

### __name__ == "__main__" (direct run) vs __name__ == module name (imported)

**__name__ == "__main__" (direct run):**
- Occurs when the file is executed directly as a script
- Code inside the `if` block runs
- Useful for testing or running the module standalone

**__name__ == module name (imported):**
- Occurs when the file is imported by another script
- Code inside the `if` block is skipped
- Prevents test code from executing unintentionally

### sys.path (module search path) vs Current working directory

**sys.path (module search path):**
- A list of directories Python searches for modules
- Can be modified programmatically with sys.path.append()
- Includes standard library and site-packages directories

**Current working directory:**
- The directory from which the top-level script was run
- Always the first entry in sys.path
- Not the same as the directory of the imported module's file

### Absolute import (e.g., from package.module import item) vs Relative import (e.g., from . import sibling)

**Absolute import (e.g., from package.module import item):**
- Uses the full dotted path from the top-level package
- Works regardless of where the importing module is located
- Clear and unambiguous, recommended for most cases

**Relative import (e.g., from . import sibling):**
- Uses dots to refer to current (.) or parent (..) packages
- Only works inside a package, not from a top-level script
- Can break if the package structure is reorganised

## Diagram

_This flowchart shows how a Python script imports modules from a package, with the `__init__.py` file initialising the package namespace and each module maintaining its own separate namespace._

```mermaid
flowchart TD
    A[Python Script] -->|import my_package| B[my_package folder]
    B --> C[__init__.py]
    B --> D[module_a.py]
    B --> E[module_b.py]
    C -->|initialises| F[Package namespace]
    D --> G[module_a namespace: func_a, var_x]
    E --> H[module_b namespace: func_b, var_y]
    A -->|from my_package.module_a import func_a| I[Current namespace: func_a]
    A -->|import my_package.module_b| J[Qualified access: my_package.module_b.func_b]
```

## Common misconceptions

- **Misconception:** If I create a folder and put my .py files in it, I can import them using 'import folder.filename'. **Reality:** For a folder to be importable as a package, it must contain an `__init__.py` file (even if empty). Without it, Python will not treat the folder as a package and will raise a `ModuleNotFoundError` when you try to import using the dot notation, unless the folder is in `sys.path` and you import the file directly without the folder prefix. (Beginners think folders are automatically recognised as packages because that is how most file systems work – they assume the OS directory structure directly maps to import paths. But Python needs the explicit marker file `__init__.py` to treat a directory as a package, to prevent accidentally importing directories that are not meant to be Python packages.)
- **Misconception:** `from module import *` imports all the sub-modules and sub-packages inside that module. **Reality:** `from module import *` only imports the names that are defined at the top level of that module's namespace, such as functions, classes, and variables. It does not automatically import sub-modules or sub-packages unless they are explicitly imported in the module's `__all__` list or the module's code itself imports them at the top level. (This mistake comes from assuming the asterisk is a 'wildcard' that recursively grabs everything, similar to how `*` works in shell globbing. In Python, `*` only applies to the names directly in that module's namespace, not the file system structure.)
- **Misconception:** When I import a module using `import modulename`, all the functions and variables become directly available without needing to prefix them with `modulename.`. **Reality:** Using `import modulename`, you must use the qualified name `modulename.function()` to access its contents. To make them available directly, you must use `from modulename import function`. (Beginners often expect that importing a module is like copying all its contents into the current file, because that is conceptually simpler. But Python deliberately uses namespaces to avoid name collisions, and the qualified access is how that protection works.)
- **Misconception:** If I import a module in two different files, Python loads and executes the module twice, once for each file. **Reality:** Modules are cached in `sys.modules`. When a module is imported for the first time, it is loaded and executed once, and its object is stored in a dictionary. Any subsequent imports of the same module, even from different files, just retrieve the cached object from `sys.modules`. The module code is not re-executed. (This misconception comes from thinking of imports as distinct operations per file, like how including a header file in C works. Python's caching mechanism is a performance optimisation that beginners do not intuitively expect.)
- **Misconception:** Changing a variable that I imported using `from module import x` inside my script will change the original variable inside the module for everyone else. **Reality:** When you use `from module import x`, you get a reference to the object that `x` points to in the module at that moment. If you reassign `x` in your script, you are just pointing your local name `x` to a new object; the module's original `x` remains unchanged. Only if you use `module.x = new_value` (with the module reference) do you change the original. (This confusion arises because beginners do not distinguish between rebinding a name and mutating an object. They think 'import x' creates a link back to the module, but it is a one-time copy of the reference.)

## Key takeaways

- A module is a single `.py` file, and a package is a directory containing an `__init__.py` file plus zero or more modules or sub-packages.
- Use `import modulename` to get a qualified namespace, and `from modulename import item` to bring an item directly into your current namespace.
- The `__init__.py` file is executed when its package is first imported, and it can be used to initialise package-level resources or import sub-modules automatically.
- Python locates modules by searching the directories listed in `sys.path` in order: the script's directory, then `PYTHONPATH`, then standard library directories, then site-packages.
- The `__name__` variable equals `"__main__"` when a file is run directly, and equals the module's name when it is imported, enabling conditional execution of test code.
- Using `from module import *` imports all names defined at the top level of that module that do not start with an underscore, unless the module defines an `__all__` list which then restricts what is imported.

## FAQ

**Why do I get 'ModuleNotFoundError' even though my .py file exists in the same folder?**

Check that the file has the `.py` extension and that you are not accidentally trying to import a module from a different working directory. Also ensure you have an `__init__.py` file if you are trying to import from a sub-directory package.

**What is the difference between 'import modulename' and 'from modulename import item'?**

`import modulename` gives you access to everything in the module, but you must prefix names with `modulename.`. `from modulename import item` brings that specific item into your current namespace so you can use it directly without a prefix.

**Can I have multiple modules with the same name in different folders?**

Yes, but Python will import only the first one it finds in `sys.path`. To avoid conflicts, use packages with `__init__.py` to create a hierarchy, and import using the full dotted path like `from mypackage.mymodule import something`.

**What does the 'if __name__ == "__main__":' line do?**

It checks whether the current file is being run directly (as a script) or being imported as a module. The code inside that block runs only when the file is executed directly, not when it is imported into another script.

**How do I import a module that is one directory level up from my current script?**

You can modify `sys.path` in your script by adding the parent directory: `import sys; sys.path.append("..")`. Then you can import the module normally. Alternatively, restructure your project into a package and use relative imports.

**Is it bad practice to use 'from module import *'?**

Yes, it is generally discouraged because it imports all names from the module into your namespace, which can accidentally overwrite your existing variables and make it unclear where a name came from. It is better to import only what you need explicitly.

## Check your understanding

1. **What file must be present inside a directory for Python to recognise it as a package?**

   Answer: An `__init__.py` file (can be empty) must be present inside the directory to signal to Python that it is a package.

2. **What is the value of `__name__` when a Python file is run directly as a script?**

   Answer: `__name__` is set to the string `"__main__"` when the file is executed directly.

3. **If you use `import modulename` and then later assign `modulename.x = 5`, does this affect other modules that import the same module?**

   Answer: Yes, because `modulename` refers to the same module object in memory, so modifying its attribute `x` is visible to all other importers of that module.

4. **What is the purpose of the `__all__` list in a module?**

   Answer: `__all__` is a list of strings that defines which names will be exported when a user writes `from module import *`. If `__all__` is not defined, all names not starting with an underscore are exported.

5. **Can you use relative imports (e.g., `from . import something`) from a top-level script that is not part of a package?**

   Answer: No, relative imports only work inside a package (i.e., when the importing module is itself imported as part of a package, not run directly as a script).

---

Interactive version with quiz and diagrams: https://courseiva.com/learn/python-pcap/modules-and-packages-101
