Python

Modules Data And Errors

Modules, Packages & Virtual Environments

A module is simply a .py file — any Python file can be imported and reused in another.

JrCodex·6 min read

Jr Codex Python Notes

Level: Intermediate Prerequisites: Module 2: Functions & Functional Basics Time to complete: ~25 minutes


Table of Contents

  1. What is a Module?
  2. Import Styles
  3. The Standard Library — A Quick Tour
  4. Packages
  5. Installing Third-Party Packages with pip
  6. Virtual Environments
  7. if __name__ == "__main__"
  8. Summary & Next Steps

1. What is a Module?

A module is simply a .py file — any Python file can be imported and reused in another.

# math_utils.py
def add(a, b):
    return a + b
 
def subtract(a, b):
    return a - b
 
PI = 3.14159
# main.py
import math_utils
 
print(math_utils.add(3, 4))         # 7
print(math_utils.PI)                  # 3.14159

Splitting code into modules is how real projects stay organized once they grow past a single script — a pattern every later module in this curriculum assumes you already know.


2. Import Styles

import math_utils                       # import the whole module, access via math_utils.add()
from math_utils import add                # import ONE name directly, use add() directly
from math_utils import add, subtract        # import MULTIPLE names
from math_utils import add as addition        # rename on import
import math_utils as mu                         # rename the whole module
from math_utils import *                          # import EVERYTHING — avoid this (see below)
StyleWhen to Use
import moduleDefault, safest — makes the source of each name explicit (module.func)
from module import nameWhen you use name very frequently and the module is unambiguous
import module as aliasConvention for well-known libraries (import numpy as np)
from module import *Avoid — pollutes your namespace, unclear where names came from
# Why `import *` is discouraged:
from math_utils import *
from other_utils import *
# If both modules define `add`, which one wins? Unclear without checking both files.

3. The Standard Library — A Quick Tour

Python ships with a huge collection of built-in modules — "batteries included." A few you'll use constantly:

import math
print(math.sqrt(16))         # 4.0
print(math.pi)                 # 3.14159...
 
import random
print(random.randint(1, 10))    # random int between 1 and 10
print(random.choice(["a", "b", "c"]))   # random item from a list
 
import datetime
today = datetime.date.today()
print(today)                       # e.g. 2026-07-03
 
import os
print(os.getcwd())               # current working directory
print(os.listdir("."))            # list files in current directory
 
import sys
print(sys.version)                  # Python version info
ModuleCommon Use
mathMathematical functions and constants
randomRandom number generation, shuffling, sampling
datetimeDates, times, durations
osFilesystem paths, environment variables, directories
sysInterpreter internals, command-line args, exit codes
jsonParsing/writing JSON (Chapter 3)
reRegular expressions

4. Packages

A package is a directory of modules, identified by containing an __init__.py file (can be empty).

my_package/
├── __init__.py
├── math_utils.py
└── string_utils.py
from my_package import math_utils
from my_package.string_utils import capitalize_words
 
math_utils.add(2, 3)

Every third-party library you'll use later (NumPy, pandas, transformers) is distributed as a package exactly like this — understanding this structure demystifies what import numpy as np is actually doing.


5. Installing Third-Party Packages with pip

pip is Python's package installer, pulling from PyPI (the Python Package Index).

pip install requests              # install the latest version
pip install requests==2.31.0        # install a specific version
pip install --upgrade requests        # upgrade to latest
pip uninstall requests                  # remove a package
pip list                                  # see installed packages
pip show requests                          # see details about one package
import requests
response = requests.get("https://api.github.com")
print(response.status_code)     # 200

requirements.txt — Sharing Dependencies

Real projects list their dependencies in a file so others (or future you) can recreate the exact environment:

# requirements.txt
requests==2.31.0
numpy==1.26.0
pandas==2.1.0
pip install -r requirements.txt      # installs everything listed
pip freeze > requirements.txt          # generate the file from what's currently installed

6. Virtual Environments

Installing packages globally on your machine causes a classic problem: Project A needs requests==2.0, Project B needs requests==2.31, and they conflict. A virtual environment gives each project its own isolated set of installed packages.

# Create a virtual environment (creates a "venv" folder)
python -m venv venv
 
# Activate it
# Windows:
venv\Scripts\activate
# macOS/Linux:
source venv/bin/activate
 
# Your prompt now shows (venv) — installs are isolated to this project
(venv) $ pip install requests
 
# Deactivate when done
(venv) $ deactivate
Without a Virtual Environment          With a Virtual Environment
─────────────────────────────         ─────────────────────────────
  Project A ──┐                          Project A → venv_a (requests 2.0)
  Project B ──┼─→ ONE global Python       Project B → venv_b (requests 2.31)
  Project C ──┘    (version conflicts!)   Project C → venv_c (own packages)
─────────────────────────────         ─────────────────────────────

Best practice: create a virtual environment for every project, and never commit the venv/ folder to version control (add it to .gitignore — only requirements.txt should be tracked).


7. if __name__ == "__main__"

You've seen this pattern since Chapter 1's first program — now the mechanism behind it:

# math_utils.py
def add(a, b):
    return a + b
 
print(f"math_utils loaded, __name__ = {__name__}")
 
if __name__ == "__main__":
    print("Running math_utils.py directly")
    print(add(2, 3))
$ python math_utils.py
math_utils loaded, __name__ = __main__
Running math_utils.py directly
5
# main.py
import math_utils     # math_utils's print() runs, but __name__ is "math_utils" here, NOT "__main__"
$ python main.py
math_utils loaded, __name__ = math_utils

Why this matters: it lets a file work both as an importable module and a standalone script — code inside the if __name__ == "__main__": block only runs when the file is executed directly, never when it's imported elsewhere.


8. Summary & Next Steps

Key Takeaways

  • A module is any .py file; a package is a directory of modules with an __init__.py.
  • Prefer import module or from module import specific_name — avoid from module import *, which hides where names come from.
  • pip installs third-party packages from PyPI; requirements.txt records exact versions for reproducibility.
  • Always use a virtual environment per project — it isolates dependencies and avoids version conflicts between projects.
  • if __name__ == "__main__": lets a file act as both a reusable module and a standalone script.

Concept Check

  1. Why is from module import * generally discouraged?
  2. What problem do virtual environments solve?
  3. What does __name__ equal when a file is run directly vs. imported by another file?

Next Chapter

Chapter 2: File Handling


Jr Codex — 1-on-1 Personalized Coaching | Back to Module Index