# Python Modules and Packages: import, pip, venv and Libraries Explained Simply

URL: https://skillaria.top/en/guides/python/python-modules-packages-venv-pip

A module in Python is a .py file, a package is a folder of modules, and a library is a set of packages that you import with the import statement. Third-party libraries are installed with pip (or uv) into a venv virtual environment — an isolated folder with its own interpreter and packages for one project, so the versions used by different projects do not conflict.

A module in Python is an ordinary file with code, and `import` is the way to take functions and classes from it. Modules are combined into packages, packages into libraries, and so that third-party libraries of different projects do not get in each other's way, they are installed into a venv virtual environment. Let us go through the whole chain: what happens during an import, how a package is structured, how pip and venv work in Python, and what lazy imports introduced in Python 3.15 are.

## Module, package, library: what is the difference

| Term | What it is | Example |
| --- | --- | --- |

| Module | A single `.py` file; the module name is the file name without the extension | `categories.py` → `import categories` |

| Package | A folder with modules and an `__init__.py` file | `expenses/report.py` → `from expenses.report import build` |

| Library | A ready-made set of modules and packages for some task | `requests`, `pandas`, the standard `pathlib` |

| Distribution (a PyPI package) | An archive that pip installs | `pip install requests` |

In conversation the words "library", "package" and "module" are often mixed up — that is normal. What matters is that technically `import` always loads a module or a package, while pip installs a distribution from PyPI, which contains packages inside it.

## How import works in Python

There are several forms of import, and all of them appear in real code:

```
import math                    # the whole module, accessed as math.sqrt
from math import sqrt, pi      # individual names
import datetime as dt          # an alias
from collections import Counter as C

print(math.sqrt(16), sqrt(9), pi)
print(dt.date.today())
```

Aliases are standard for large libraries: `import numpy as np`, `import pandas as pd`. It is better not to use the `from module import *` construction: it clutters the namespace and hides where a name came from.

### What import actually does

1. It looks for the module in the `sys.modules` cache. If it is already loaded, it simply returns it.
2. If not, it searches for the file in the list of folders `sys.path`: the folder of the running script, the standard library, the site-packages of the environment.
3. It executes the found file as a whole, from top to bottom.
4. It puts the module object into the cache and binds it to the name.

Two practical conclusions follow. A repeated `import` of the same module is almost free — the code is not executed a second time. And all the top-level code in a module, including `print` and heavy calculations, runs at import time.

```
import sys
for p in sys.path:
    print(p)
print("math" in sys.modules)
```

### if __name__ == "__main__"

Since an import executes the file, there must be a way to tell "I was run as a script" from "I was imported". This is what the `__name__` variable is for: when the file is run, it equals `"__main__"`; when imported, it equals the module name.

```
# categories.py
def guess(title: str) -> str:
    return "food" if "cafe" in title.lower() else "other"

if __name__ == "__main__":
    print(guess("Cafe near home"))   # runs only with python categories.py
```

For a package there is an alternative: a `__main__.py` file inside it and the launch `python -m expenses`.

## Standard Python modules

Python comes "with batteries included": hundreds of built-in modules are available right away, without installation. The most popular ones:

| Module | What for |
| --- | --- |

| `os`, `sys`, `pathlib`, `shutil` | System, paths, files |

| `json`, `csv`, `pickle` | Data formats |

| `datetime`, `time`, `calendar` | Date and time |

| `math`, `random`, `statistics`, `fractions` | Numbers |

| `re`, `string` | Working with text |

| `collections`, `itertools`, `functools` | Data structures and iterators |

| `logging`, `argparse`, `subprocess` | Logs, command-line arguments, running programs |

| `asyncio`, `threading` | Asynchronous code and threads |

The full list of standard library modules is stored in `sys.stdlib_module_names` (Python 3.10+). To see what a module contains, use `dir(math)` and `help(math)`.

## Your own package: project structure

When there are many modules, they are grouped into a package. A common layout is the src-layout:

```
expenses-project/
├── pyproject.toml
└── src/
    └── expenses/
        ├── __init__.py
        ├── cli.py
        ├── parsing.py
        ├── categories.py
        └── report.py
```

Inside a package, modules import each other either through the package name (`from expenses.categories import guess`) or relatively (`from .categories import guess`). A relative import works only inside a package, not in a file run directly as a script.

## pip in Python: installing libraries

Third-party libraries live in the PyPI catalogue, and they are installed by the pip utility, which comes with Python. The most reliable way is to call it through the interpreter — that way the library lands in exactly the Python you run your code with.

```
python -m pip install requests          # install
python -m pip install "pandas>=2.3"     # with a version constraint
python -m pip install --upgrade requests
python -m pip uninstall requests        # remove
python -m pip list                      # list installed packages
python -m pip show requests             # version and dependencies
python -m pip freeze > requirements.txt
python -m pip install -r requirements.txt
```

On Windows the `py` launcher is often used instead of `python`. If pip is not found, the built-in module installs it: `python -m ensurepip --upgrade`.

### Popular Python libraries

There are hundreds of thousands of third-party packages on PyPI, but most projects use the same short list. Start with it rather than searching for exotic options.

| Task | Libraries |
| --- | --- |

| HTTP requests and APIs | `requests`, `httpx` |

| Data analysis and tables | `pandas`, `numpy` |

| Charts | `matplotlib` |

| Web services | `fastapi`, `django`, `flask` |

| Tests | `pytest` |

| Images | `pillow` (imported as `PIL`) |

Note that the name used for installation and the name used for import do not always match. You install with `pip install pillow` but write `from PIL import Image`; you install `opencv-python` but import `cv2`. If `import` fails right after installation, check under which name the package is imported in its documentation.

### Installing libraries on Linux

On recent Debian and Ubuntu, `pip install` into the system Python ends with the `externally-managed-environment` error (PEP 668). This is a safeguard: the system Python belongs to the OS package manager, and pip must not break it. The right solution is not `--break-system-packages` but a virtual environment.

## venv in Python: a virtual environment

A virtual environment is a separate folder with its own copy of the interpreter (a link to it) and its own site-packages. Libraries installed in it are visible only to this project. So one project can use Django 4 while a neighbouring project uses Django 5, and they do not conflict.

The `venv` module is built into Python and does not need to be installed separately (on Debian/Ubuntu the system package `python3-venv` is sometimes required).

```
# create an environment in the .venv folder
python -m venv .venv

# activate: Linux and macOS
source .venv/bin/activate
# activate: Windows PowerShell
.venv\Scripts\Activate.ps1

# now pip installs packages into .venv
python -m pip install requests
python -c "import sys; print(sys.prefix)"

# leave the environment
deactivate
```

After activation, `(.venv)` appears at the start of the terminal line. The environment folder is not committed to git (add `.venv/` to `.gitignore`) and is not moved between machines — instead you record the list of dependencies and recreate the environment. If PowerShell blocks running the activation script, allow it for the current user: `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned`.

### uv and pyproject.toml

Modern projects describe their dependencies in `pyproject.toml`. The uv manager, which we use in the course, combines creating the environment and installing: `uv add pandas` downloads the package, writes it into `pyproject.toml` and pins exact versions in `uv.lock`. A colleague clones the project, runs `uv sync` — and gets an identical environment.

```
[project]
name = "expenses"
version = "0.1.0"
requires-python = ">=3.15"
dependencies = [
    "pandas>=2.3",
]

[project.scripts]
expenses = "expenses.cli:main"
```

## Lazy imports in Python 3.15 (PEP 810)

The main novelty of 3.15 on this topic is the `lazy` keyword. Heavy libraries take a long time to load: in a measurement made for the course on the `python:3.15-slim` image, a regular `import pandas` took about 415 ms, while `lazy import pandas` took about 13 ms. For a CLI utility that is run just to show `--help`, the difference is huge.

```
lazy import pandas as pd
lazy from expenses.report import build_report

def main() -> None:
    ...
    df = pd.read_csv(path)   # the real loading of pandas happens here
```

Instead of the module, a proxy object (`types.LazyImportType`) is bound to the name, and the real import happens at the first access to the name. If that access never happens, no time is spent at all.

### Limitations of lazy import

- Allowed only at the top level of a module: inside a function, a class or a `try/except` it is a `SyntaxError`.
- `lazy from x import *` is forbidden.
- `ModuleNotFoundError` surfaces not at start-up but at the first use.
- Side effects of a module (registering plugins and so on) shift in time.

To make all imports lazy at once, use the flag `-X lazy_imports=all` or the variable `PYTHON_LAZY_IMPORTS=all`; exceptions are set through `sys.set_lazy_imports_filter()`. For code that must also work on older versions there is the module variable `__lazy_modules__ = ["pandas"]`: 3.15 makes the listed imports lazy, while older versions simply perform a regular `import`. To see where start-up time goes, use `python -X importtime -c "import pandas"`. Programs that are launched often and briefly benefit most; a long-running server gains almost nothing from laziness.

## Common mistakes

- `ModuleNotFoundError: No module named 'requests'` — the library was installed into a different Python, or the environment is not activated. Check with `python -m pip show requests` using the same interpreter.
- Your own file is named `json.py`, `random.py` or `csv.py` — the script's folder comes first in `sys.path`, and it shadows the standard module. Python 3.13+ suggests "consider renaming" in the error. Rename the file and delete `__pycache__`.
- Circular import: `a.py` imports `b.py`, which imports `a.py`. Move the shared code into a third module or import inside a function.
- pip run inside the Python interpreter (`>>> pip install`) — you get a `SyntaxError`. pip commands are executed in the terminal.

## Summary

A module is a file, a package is a folder of modules, and a library is a ready-made set of them; `import` looks for a module in the cache and along `sys.path`, and executes it once. Install third-party libraries with pip or uv only into a venv virtual environment — that way projects do not get in each other's way, and the environment is easy to reproduce. In our Python course this topic is practised hands-on: the expense tracker becomes a package with dependencies and starts quickly thanks to lazy imports.

## FAQ

**Do I need to create a venv for every project?**

Yes, it is good practice: each project has its own library versions, and a separate environment prevents conflicts and keeps the system Python clean.

**How is pip different from uv?**

pip only installs packages, while uv also creates the environment, records dependencies in pyproject.toml and pins exact versions in a lock file; it is also noticeably faster.

**How do I see the list of installed Python libraries?**

Run python -m pip list in the right environment; pip freeze prints the same list in requirements.txt format.

**Where are installed modules stored?**

In the site-packages folder of the current environment; the path to a specific module is shown by the __file__ attribute, for example import requests; print(requests.__file__).

**Can I move a venv folder to another computer?**

No, the environment is tied to the machine's paths and interpreter; you move the list of dependencies and create the environment again.


---
Source: Skillaria.Top (https://skillaria.top)
