Session 21 - Modules, Packages & pip

Session 21 of the Python Fundamentals series, covered in depth. It reuses code beyond a single file, covering imports from the standard library in all their forms - import math, from math import sqrt, import x as np, and from x import * - then writing and importing your own module, the if __name__ == '__main__' guard, and packages as folders of modules. It goes on to installing third-party code with pip and requirements.txt, isolating projects with virtual environments, and the import search path. The traps are ModuleNotFoundError, shadowing a standard-library name by calling your file random.py, and running work at import time. Every snippet and error message was executed and copied verbatim from CPython 3.12.

Subject: Python Fundamentals · 98 slides · code lesson

Open the interactive version of this deck · Homework for this lesson

What this lesson covers

The lesson, slide by slide

1. Modules, Packages & pip

Title

Python Fundamentals - Session 21

Stop rewriting code - borrow it, split it, and share it

2. What you will be able to do

Objectives

Until now every program lived in one file and used only built-in tools. This session opens the door to the rest of Python's code - and your own.

  1. import a module and use it with dot notation (math.sqrt).
  2. Pull single names in with from math import sqrt, and rename with as.
  3. Split your own code into a module file and import it.
  1. Explain the if __name__ == "__main__": guard and why it matters.
  2. Install third-party packages with pip and pin them in requirements.txt.
  3. Avoid the classic traps: ModuleNotFoundError and shadowing a stdlib name.

3. What survived from Session 20 - CSV & JSON?

Warm-up

Discussion prompt

Before we open Session 21 - Modules, Packages & pip: without looking back, what was the main idea of Session 20 - CSV & JSON, and what could you do by the end of it that you could not do before?

Hint: One sentence for the idea, one for the skill. If the second one is blank, that is the part to revisit.

Answer:

Session 20 of the Python Fundamentals series, in depth. Saving and loading structured data with two standard-library modules: json (dumps/loads for strings, dump/load for files, and the Python-to-JSON type map where True becomes true and None becomes null) and csv (reader/writer, DictReader/DictWriter, and the newline='' rule when you open the file).

4. The import Statement

Section

Part 1

5. A module is a file full of ready-made tools

Concept

Python ships with a huge standard library - hundreds of modules you never have to install. math, random, datetime, statistics are all there, waiting.

module — A file of Python code (functions, variables, classes) you can pull into your program with import. The standard library is a big collection of them.

6. Break it if you can: A module is a file full of ready-made tools

Counterexample

Discussion prompt

Python ships with a huge standard library - hundreds of modules you never have to install. math, random, datetime, statistics are all there, waiting.

That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.

Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.

7. import loads the module

Concept

import math runs the module once and gives you a name (math) to reach its contents. You do this once, near the top of your file.

After that, everything inside lives behind the math. prefix.

8. By analogy: import loads the module

Analogy

Discussion prompt

Explain import loads the module by analogy to something with no Python Fundamentals in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.

Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.

Answer:

import math runs the module once and gives you a name (math) to reach its contents. You do this once, near the top of your file.

9. What has to happen first: import math and use it

Ranking

Put in order

Put the moves of import math and use it into the order they have to happen.

  1. Line 1 loads the module
  2. Reach each tool through math.
  3. Read the output

Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. One import at the top makes the whole math module available below.

10. import math and use it

Worked example

import math

print(math.sqrt(16))
print(math.pi)
print(math.floor(3.7))

Line 1 loads the module

Why: One import at the top makes the whole math module available below.

Reach each tool through math.

Why: sqrt is a function; pi is a stored value; floor rounds down.

Read the output

Why: Verified by execution: 4.0, then 3.141592653589793, then 3.

expressionprints
math.sqrt(16)4.0
math.pi3.141592653589793
math.floor(3.7)3

11. Fill in: prints for import math and use it

Comparison

Comparison matrix

From import math and use it: refill the prints column from what you know. The rest of the table is as it appeared.

expressionprints
math.sqrt(16)4.0
math.pi3.141592653589793
math.floor(3.7)3

12. Dot notation: which module, which tool

Concept

math.sqrt reads left to right: 'in the math module, the sqrt name'. The dot keeps names organized so two modules can each have a sqrt without clashing.

13. Teach it back: Dot notation: which module, which tool

Explain it

Discussion prompt

Explain Dot notation: which module, which tool to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.

Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.

Answer:

math.sqrt reads left to right: 'in the math module, the sqrt name'. The dot keeps names organized so two modules can each have a sqrt without clashing.

14. Predict the next row: One import, many tools

Pattern

Predict first

The table runs: math.gcd(12, 18) | greatest common divisor | 6 · math.factorial(5) | 5 x 4 x 3 x 2 x 1 | 120 · math.ceil(4.1) | round up | 5

In One import, many tools, given the rows so far: what is the next one — the row where call is math.floor(4.9)?

Correct: math.floor(4.9) | round down | 4

callmeaningprints
math.gcd(12, 18)greatest common divisor6
math.factorial(5)5 x 4 x 3 x 2 x 1120
math.ceil(4.1)round up5
math.floor(4.9)round down4

Why: The relationship between the columns, not the individual numbers, is what generates the next row. gcd, factorial, ceil, floor all live in math - no extra imports needed.

15. One import, many tools

Worked example

import math

print(math.gcd(12, 18))
print(math.factorial(5))
print(math.ceil(4.1))
print(math.floor(4.9))

The single import unlocks all of them

Why: gcd, factorial, ceil, floor all live in math - no extra imports needed.

Trace each call

Why: Verified by execution: 6, 120, 5, 4. This table walks every call in the file.

callmeaningprints
math.gcd(12, 18)greatest common divisor6
math.factorial(5)5 x 4 x 3 x 2 x 1120
math.ceil(4.1)round up5
math.floor(4.9)round down4

16. What each one costs: One import, many tools

Trade off

Comparison matrix

From One import, many tools: every row here is a choice with a cost. Fill the meaning column, then say which row you would actually pick and what you give up for it.

callmeaningprints
math.gcd(12, 18)greatest common divisor6
math.factorial(5)5 x 4 x 3 x 2 x 1120
math.ceil(4.1)round up5
math.floor(4.9)round down4

17. from, as, and *

Section

Part 2

18. from ... import pulls a name in directly

Concept

from math import sqrt copies just sqrt into your file. Now you write sqrt(25) with no prefix - handy when you use one tool a lot.

The trade-off: the reader no longer sees where sqrt came from.

19. from math import sqrt, pi

Worked example

from math import sqrt, pi

print(sqrt(9))
print(pi)

Import two names at once

Why: Separate them with commas; both land in your file directly.

Call them with no math. prefix

Why: Verified by execution: 3.0, then 3.141592653589793.

you writeprints
sqrt(9)3.0
pi3.141592653589793

20. Inspect it line by line: from math import sqrt, pi

Error analysis

Annotate

Walk the callouts on from math import sqrt, pi. Each one is a place this is easy to get subtly wrong.

  • Separate them with commas; both land in your file directly.
  • Verified by execution: 3.0, then 3.141592653589793.

21. Two doors into the same room

Intuition

import math is standing in the hallway and reaching through the math. door each time. from math import sqrt is carrying sqrt out into your own room so it is right there.

Same tool, same result - just how far you reach for it. Reach through the prefix when you want the origin visible; bring it in when you use it constantly.

22. as renames on the way in

Concept

import numpy as np imports the module but calls it np in your file. Aliases save typing and follow community conventions (np, pd, plt).

alias — A shorter or conventional name for an imported module, set with as. import numpy as np means 'load numpy, but I'll call it np'.

23. Take the definitions apart: module vs alias

Definition probe

Sort into buckets

Every line below is part of the definition of module or of alias — one or the other, never both. Put each where it belongs.

module
A file of Python code (functions, variables, classes) you can pull into your program with import.; The standard library is a big collection of them.
alias
A shorter or conventional name for an imported module, set with as.; import numpy as np means 'load numpy, but I'll call it np'.
b1
A file of Python code (functions, variables, classes) you can pull into your program with import. The standard library is a big collection of them.
b2
A shorter or conventional name for an imported module, set with as. import numpy as np means 'load numpy, but I'll call it np'.

24. import math as m

Worked example

import math as m

print(m.sqrt(49))
print(m.pi)

m is now the module

Why: The alias replaces the name math everywhere in this file.

Use the alias with dot notation

Why: Verified by execution: 7.0, then 3.141592653589793. Same module, shorter name.

you writeprints
m.sqrt(49)7.0
m.pi3.141592653589793

25. Draw the shape of it: import math as m

Blank canvas

Draw it

Draw what import math as m just did — the shape of it, not the line-by-line working. One picture, labels only where you need them. Then check it against the steps: anything you could not draw is a step you followed rather than understood.

26. from x import * dumps everything

Concept

from math import * brings in every public name at once - sqrt, pi, floor, and dozens more - each with no prefix.

It reads short but hides origins and can quietly overwrite your own names. Convenient in a quick experiment; avoid it in real programs.

27. from math import *

Worked example

from math import *

print(sqrt(16))
print(floor(2.9))

Every math name is now loose in your file

Why: sqrt and floor work with no prefix - but so do dozens of names you never asked for.

Read the output

Why: Verified by execution: 4.0, then 2. It works, but a reader cannot tell these came from math.

you writeprints
sqrt(16)4.0
floor(2.9)2

28. Writing Your Own Module

Section

Part 3

29. Your .py file is already a module

Concept

There is nothing special about standard-library modules. Any .py file can be imported by another file in the same folder, using its name without the .py.

So a file mymath.py becomes import mymath next door.

30. Guess the shape of the answer: Split code into mymath.py

Estimation

Predict first

Two files in one folder. First, mymath.py:

Commit before you compute: what does Split code into mymath.py come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.

Correct: Running main.py prints three lines

Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. Verified by execution: 'mymath loaded' (the import ran the print), then 7, then 20.

31. Split code into mymath.py

Worked example

Two files in one folder. First, mymath.py:

def add(a, b):
    return a + b

def double(n):
    return n * 2

print("mymath loaded")

Then main.py imports it

Why: import mymath runs the whole file top to bottom, then hands you the mymath name.

filecontents
mymath.pytwo functions + a print
main.pyimport mymath; call add, double

Running main.py prints three lines

Why: Verified by execution: 'mymath loaded' (the import ran the print), then 7, then 20.

source of the lineprints
import mymath (import-time print)mymath loaded
mymath.add(3, 4)7
mymath.double(10)20

32. Importing runs the file - once

Concept

The mymath loaded line proves it: import mymath executes the module top to bottom. Definitions get made, and any loose statements (like that print) actually run.

Import the same module twice and it still runs only once - Python caches it. Keep that print in mind; we will fix it with a guard shortly.

33. Where does each piece belong: Session 21 - Modules, Packages & pip

Sorting

Sort into buckets

These are the pieces of Session 21 - Modules, Packages & pip, out of order. Put each one back under the part of the lesson it belongs to.

The import Statement
A module is a file full of ready-made tools; import loads the module; import math and use it
from, as, and *
from ... import pulls a name in directly; from math import sqrt, pi; Two doors into the same room
Writing Your Own Module
Your .py file is already a module; Split code into mymath.py; Importing runs the file - once
s1
The import Statement is where Session 21 - Modules, Packages & pip puts A module is a file full of ready-made tools, import loads the module, import math and use it. Knowing which part of the lesson a problem belongs to is most of knowing which method to reach for.
s2
from, as, and * is where Session 21 - Modules, Packages & pip puts from ... import pulls a name in directly, from math import sqrt, pi, Two doors into the same room. Knowing which part of the lesson a problem belongs to is most of knowing which method to reach for.
s3
Writing Your Own Module is where Session 21 - Modules, Packages & pip puts Your .py file is already a module, Split code into mymath.py, Importing runs the file - once. Knowing which part of the lesson a problem belongs to is most of knowing which method to reach for.

34. Restore the missing line: Pull just the functions you need

Fill the middle

Fill in the blanks

From Pull just the functions you need — one line has had its right-hand side removed. Put it back.

from statistics import mean, median

scores = [72, 85, 90, 60]
print(mean(scores))
print(median(scores))

Why: scores is what everything below it consumes, so the wrong expression here fails later and somewhere else. statistics is a standard-library module; mean and median come in with no prefix.

35. Pull just the functions you need

Worked example

from statistics import mean, median

scores = [72, 85, 90, 60]
print(mean(scores))
print(median(scores))

from-import works on stdlib and your modules alike

Why: statistics is a standard-library module; mean and median come in with no prefix.

Compute over the list

Why: Verified by execution: mean is 76.75, median is 78.5 (average of the middle two once sorted).

callprints
mean([72, 85, 90, 60])76.75
median([72, 85, 90, 60])78.5

36. The __name__ Guard

Section

Part 4

37. Every file has a __name__

Concept

Python sets a hidden variable __name__ in every file. When you run a file directly, its __name__ is the string "__main__".

But when that same file is imported, its __name__ is the module's name instead. That one difference is the whole trick.

38. Watch __name__ change

Worked example

A file greet.py that just prints its own __name__:

def greet(name):
    return "Hello, " + name

print(__name__)

Run greet.py directly

Why: Executed as the main program, so __name__ is "__main__".

Import greet from another file

Why: Verified by execution: run directly prints __main__; imported prints greet (then greet("Sam") gives Hello, Sam).

how greet.py is usedprint(__name__) shows
python greet.py (run directly)__main__
import greet (from another file)greet

39. "Only if I'm the one being run"

Intuition

Think of __name__ == "__main__" as a file asking: 'Am I the star of the show right now, or just a supporting tool someone imported?'

Code under that guard runs only when the file is the star. Imported as a helper, the guarded block stays quiet - so importing never triggers a demo, a test, or a menu you did not ask for.

40. The guard: if __name__ == "__main__":

Concept

Put your 'run this file directly' code - demos, quick tests, a main menu - inside if __name__ == "__main__":. Keep your reusable defs outside it.

Now the file works two ways: run it to see the demo, or import it to borrow just the functions.

41. What has to happen first: A guarded module

Ranking

Put in order

Put the moves of A guarded module into the order they have to happen.

  1. The def is always available
  2. The guarded demo runs only when run directly
  3. Compare the two uses

Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. to_fahrenheit is defined whether the file is run or imported.

42. A guarded module

Worked example

def to_fahrenheit(c):
    return c * 9 / 5 + 32

if __name__ == "__main__":
    print(to_fahrenheit(100))

The def is always available

Why: to_fahrenheit is defined whether the file is run or imported.

The guarded demo runs only when run directly

Why: Line 5 fires only if __name__ is "__main__".

Compare the two uses

Why: Verified by execution: run directly prints 212.0; imported, the guard is skipped, so nothing auto-runs and to_fahrenheit(0) gives 32.0 when you call it.

how it is usedguard blockresult
run directlyruns212.0
importedskippedsilent; to_fahrenheit(0) is 32.0 when called

43. Packages: Folders of Modules

Section

Part 5

44. A package is a folder of modules

Concept

When a project grows past one file, group related modules in a folder. That folder is a package, and you reach into it with more dots: package.module.tool.

package — A folder that holds modules (and possibly sub-folders). Import a module out of it with dotted names, e.g. from shapes import circle.

45. What has to be given first: A shapes package

Missing information

Discussion prompt

A folder shapes/ holding two modules, imported from a file beside the folder:

What do you need to know — or decide — before the first line can be written? List everything the problem has to hand you.

Hint: Anything you would have to invent to get started is a thing the problem must supply.

Answer:

shapes/circle.py and shapes/rectangle.py each define an area function.

46. A shapes package

Worked example

A folder shapes/ holding two modules, imported from a file beside the folder:

from shapes import circle, rectangle

print(round(circle.area(2), 2))
print(rectangle.area(3, 4))

The folder is the package; the files are its modules

Why: shapes/circle.py and shapes/rectangle.py each define an area function.

pathrole
shapes/the package (folder)
shapes/circle.pya module (uses math.pi)
shapes/rectangle.pya module

Call each module's area

Why: Verified by execution: circle.area(2) is 12.566..., rounded to 12.57; rectangle.area(3, 4) is 12.

callprints
round(circle.area(2), 2)12.57
rectangle.area(3, 4)12

47. Fill in: role for A shapes package

Comparison

Comparison matrix

From A shapes package: refill the role column from what you know. The rest of the table is as it appeared.

pathrole
shapes/the package (folder)
shapes/circle.pya module (uses math.pi)
shapes/rectangle.pya module

48. Dots stack for depth

Concept

shapes.circle.area is the same left-to-right reading, just deeper: package shapes, module circle, function area. Add folders, add dots.

This is exactly how big libraries are laid out - os.path.join, urllib.request.urlopen. Nothing new, just nesting.

49. __init__.py marks the folder

Concept

Traditionally a package folder holds a file named __init__.py (often empty). Its presence tells older tools 'this folder is a package', and any code inside it runs when the package is first imported.

Modern Python can import some folders without it, but adding an empty __init__.py is the safe, universally understood habit.

50. pip & requirements

Section

Part 6

51. pip installs code you did not write

Concept

The standard library is huge, but not everything. pip is Python's installer: it downloads third-party packages from PyPI (the Python Package Index) so you can import them.

pip — The command-line tool that installs third-party packages from PyPI into your Python. You run it in the terminal, not inside a .py file.

52. Guess the shape of the answer: Installing a package

Estimation

Predict first

These are terminal commands (shell), not Python. The recommended form calls pip through the exact Python you are using:

Commit before you compute: what does Installing a package come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.

Correct: python -m pip guarantees the right pip

Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. It installs into the same Python that runs your code, avoiding 'installed but still ModuleNotFoundError' confusion.

53. Installing a package

Worked example

These are terminal commands (shell), not Python. The recommended form calls pip through the exact Python you are using:

python -m pip install requests

# then, inside your .py file:
# import requests

python -m pip guarantees the right pip

Why: It installs into the same Python that runs your code, avoiding 'installed but still ModuleNotFoundError' confusion.

commandwhat it does
python -m pip install requestsdownload + install requests from PyPI
import requestsuse it in your program afterward
python -m pip uninstall requestsremove it

54. Work backwards from the answer: Installing a package

Reverse engineer

Discussion prompt

Work backwards. The example finished here:

python -m pip guarantees the right pip

What was it asked to do, and what must it have been given? Reconstruct the problem from its answer.

Hint: Every quantity in the result had to enter somewhere. Account for each one.

Answer:

These are terminal commands (shell), not Python. The recommended form calls pip through the exact Python you are using:

55. requirements.txt pins your dependencies

Concept

A requirements.txt file lists the packages (and versions) your project needs, one per line. Anyone can recreate your setup with one command.

It turns 'works on my machine' into 'works on any machine' - the file travels with your code.

56. Teach it back: requirements.txt pins your dependencies

Explain it

Discussion prompt

Explain requirements.txt pins your dependencies to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.

Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.

Answer:

A requirements.txt file lists the packages (and versions) your project needs, one per line. Anyone can recreate your setup with one command.

57. What has to be given first: Freeze and restore

Missing information

Discussion prompt

A tiny requirements.txt, then the commands that create and consume it:

What do you need to know — or decide — before the first line can be written? List everything the problem has to hand you.

Hint: Anything you would have to invent to get started is a thing the problem must supply.

Answer:

freeze snapshots your current packages into the file; install -r reinstalls exactly that list elsewhere.

58. Freeze and restore

Worked example

A tiny requirements.txt, then the commands that create and consume it:

# requirements.txt
requests==2.31.0
numpy==1.26.4

# save what is installed:
# python -m pip freeze > requirements.txt
# install everything listed:
# python -m pip install -r requirements.txt

freeze writes, -r reads

Why: freeze snapshots your current packages into the file; install -r reinstalls exactly that list elsewhere.

commanddirectioneffect
pip freeze > requirements.txtwriterecord installed packages + versions
pip install -r requirements.txtreadinstall every package listed
==2.31.0pinlock an exact version for reproducibility

59. Virtual Environments

Section

Part 7

60. Why isolate a project

Concept

Install everything into one global Python and projects collide: project A needs an old version, project B needs a new one. A virtual environment gives each project its own private box of packages.

virtual environment — A self-contained folder with its own Python and its own installed packages, so one project's dependencies never disturb another's.

61. Term to definition: Session 21 - Modules, Packages & pip

Matching

Match the pairs

Match each term to the definition this lesson gave it — not the one you would guess from the word.

  • t1. module
  • t2. alias
  • t3. package
  • t4. pip
  • t5. virtual environment
  • d1. A file of Python code (functions, variables, classes) you can pull into your program with import. The standard library is a big collection of them.
  • d2. A shorter or conventional name for an imported module, set with as. import numpy as np means 'load numpy, but I'll call it np'.
  • d3. A folder that holds modules (and possibly sub-folders). Import a module out of it with dotted names, e.g. from shapes import circle.
  • d4. The command-line tool that installs third-party packages from PyPI into your Python. You run it in the terminal, not inside a .py file.
  • d5. A self-contained folder with its own Python and its own installed packages, so one project's dependencies never disturb another's.

Why: These are the working definitions of module, alias, package, pip, virtual environment as Session 21 - Modules, Packages & pip uses them. Pairing them correctly is the test of whether you could state each one with the slide switched off.

62. Guess the shape of the answer: Create and activate a venv

Estimation

Predict first

Terminal commands. python -m venv builds the box; activating it points your terminal at that box's Python:

Commit before you compute: what does Create and activate a venv come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.

Correct: Build, activate, then install

Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. After activating, pip and python refer to the venv, so installs stay local to this project.

63. Create and activate a venv

Worked example

Terminal commands. python -m venv builds the box; activating it points your terminal at that box's Python:

python -m venv .venv

# Windows:
.venv\Scripts\activate

# macOS / Linux:
source .venv/bin/activate

# now pip installs land inside .venv:
pip install requests

Build, activate, then install

Why: After activating, pip and python refer to the venv, so installs stay local to this project.

commandwhat happens
python -m venv .venvcreate a private environment folder
.venv\Scripts\activateactivate it (Windows)
source .venv/bin/activateactivate it (macOS / Linux)
deactivateleave the venv, back to global Python

64. Work backwards from the answer: Create and activate a venv

Reverse engineer

Discussion prompt

Work backwards. The example finished here:

Build, activate, then install

What was it asked to do, and what must it have been given? Reconstruct the problem from its answer.

Hint: Every quantity in the result had to enter somewhere. Account for each one.

Answer:

Terminal commands. python -m venv builds the box; activating it points your terminal at that box's Python:

65. The everyday workflow

Concept

The habit for any real project: create a venv, activate it, pip install what you need, then pip freeze > requirements.txt. Commit the requirements file, not the .venv folder.

A teammate clones your code, makes their own venv, and runs pip install -r requirements.txt - identical setup, zero collisions.

66. By analogy: The everyday workflow

Analogy

Discussion prompt

Explain The everyday workflow by analogy to something with no Python Fundamentals in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.

Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.

Answer:

The habit for any real project: create a venv, activate it, pip install what you need, then pip freeze > requirements.txt. Commit the requirements file, not the .venv folder.

67. The Import Search Path

Section

Part 8

68. Where Python looks for a module

Concept

When you write import x, Python searches a list of folders in order and uses the first x it finds: your script's own folder first, then the standard library, then installed packages (site-packages).

sys.path — The ordered list of folders Python searches for imports. import sys; print(sys.path) shows it. First match wins.

69. sys.path is just a list

Worked example

import sys

print(type(sys.path))
print(len(sys.path) > 0)

It is an ordinary Python list

Why: You have used lists for sessions - this is one of them, holding folder-path strings.

Two facts you can rely on

Why: Verified by execution: type is <class 'list'>, and it is non-empty. Your script's folder is searched first - which sets up the next section's traps.

expressionprints
type(sys.path)<class 'list'>
len(sys.path) > 0True

70. Draw the shape of it: sys.path is just a list

Blank canvas

Draw it

Draw what sys.path is just a list just did — the shape of it, not the line-by-line working. One picture, labels only where you need them. Then check it against the steps: anything you could not draw is a step you followed rather than understood.

71. A shelf-by-shelf search

Intuition

Picture Python looking for a book. It checks your desk first (the script's folder), then the built-in shelf (the standard library), then the shelf of things you installed. It grabs the first copy it finds and stops.

Two consequences: if the book is nowhere, you get an error; and if a book on your desk has the same title as a library book, your desk copy wins. Both are the traps ahead.

72. Break it if you can: A shelf-by-shelf search

Counterexample

Discussion prompt

Two consequences: if the book is nowhere, you get an error; and if a book on your desk has the same title as a library book, your desk copy wins. Both are the traps ahead.

That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.

Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.

73. Traps

Section

Part 9

74. Something is wrong here: ModuleNotFoundError

Anomaly

Predict first

A student writes this, and it looks reasonable:

You import a package you never installed (or misspelled).

It is wrong. Say what breaks — and say it before you turn the page.

Correct: No superhelper on your desk, in the stdlib, or in site-packages - so the import itself fails before line 3.

Install it first (in your active venv), then import.

Why: No superhelper on your desk, in the stdlib, or in site-packages - so the import itself fails before line 3.

75. Trap: ModuleNotFoundError

Trap

The trap

You import a package you never installed (or misspelled).

import superhelper

print(superhelper.run())

Python searches every folder and finds nothing

Why: No superhelper on your desk, in the stdlib, or in site-packages - so the import itself fails before line 3.

lineresult
import superhelperModuleNotFoundError: No module named 'superhelper'

The fix

Install it first (in your active venv), then import.

python -m pip install superhelper
# then in your .py file:
#   import superhelper

Install, then the import resolves

Why: Real error, verbatim: ModuleNotFoundError: No module named 'superhelper'. The fix is install the package (check the spelling and that your venv is active), not edit the import line.

causefix
not installedpython -m pip install <name>
misspelled namecorrect the spelling
wrong environmentactivate the venv that has it

76. Inspect it line by line: Trap: ModuleNotFoundError

Error analysis

Annotate

Walk the callouts on Trap: ModuleNotFoundError. Each one is a place this is easy to get subtly wrong.

  • No superhelper on your desk, in the stdlib, or in site-packages - so the import itself fails before line 3.
  • Real error, verbatim: ModuleNotFoundError: No module named 'superhelper'. The fix is install the package (check the spelling and that your venv is active), not edit the import line.

77. Something is wrong here: shadowing a stdlib name

Anomaly

Predict first

A student writes this, and it looks reasonable:

You name your own file random.py, then import random elsewhere in the same folder.

It is wrong. Say what breaks — and say it before you turn the page.

Correct: sys.path checks your folder first, so import random finds YOUR random.py - which has no randint - instead of the standard library's.

Never name a file after a module you import. Rename it.

Why: sys.path checks your folder first, so import random finds YOUR random.py - which has no randint - instead of the standard library's.

78. Trap: shadowing a stdlib name

Trap

The trap

You name your own file random.py, then import random elsewhere in the same folder.

# your file is named random.py
import random

print(random.randint(1, 6))

Your desk copy wins the search

Why: sys.path checks your folder first, so import random finds YOUR random.py - which has no randint - instead of the standard library's.

lineresult
import randomloads your random.py, not the stdlib one
random.randint(1, 6)AttributeError: module 'random' has no attribute 'randint'

The fix

Never name a file after a module you import. Rename it.

# rename your file to dice_game.py
import random

print(random.randint(1, 6))

A non-colliding name lets the real module load

Why: Real error, verbatim: AttributeError: module 'random' has no attribute 'randint'. Once your file is renamed (and any stale random.pyc removed), the stdlib random loads and randint works.

file nameimport random finds
random.pyyour file (no randint) - AttributeError
dice_game.pythe standard-library random

79. What each one costs: Trap: shadowing a stdlib name

Trade off

Comparison matrix

From Trap: shadowing a stdlib name: every row here is a choice with a cost. Fill the import random finds column, then say which row you would actually pick and what you give up for it.

file nameimport random finds
random.pyyour file (no randint) - AttributeError
dice_game.pythe standard-library random

80. Something is wrong here: work that runs at import time

Anomaly

Predict first

A student writes this, and it looks reasonable:

A module runs a demo (or asks for input) at the top level, with no guard.

It is wrong. Say what breaks — and say it before you turn the page.

Correct: import runs the whole file, so that loose print executes just from importing - noise you never wanted in another program.

Put run-directly code behind the __name__ guard.

Why: import runs the whole file, so that loose print executes just from importing - noise you never wanted in another program.

81. Trap: work that runs at import time

Trap

The trap

A module runs a demo (or asks for input) at the top level, with no guard.

def to_fahrenheit(c):
    return c * 9 / 5 + 32

print(to_fahrenheit(100))

Importing it fires the print unexpectedly

Why: import runs the whole file, so that loose print executes just from importing - noise you never wanted in another program.

actionunwanted effect
import this moduleprints 212.0 during import

The fix

Put run-directly code behind the __name__ guard.

def to_fahrenheit(c):
    return c * 9 / 5 + 32

if __name__ == "__main__":
    print(to_fahrenheit(100))

Now importing is silent

Why: Verified by execution: run directly still prints 212.0; imported, the guard is skipped and nothing auto-runs. Definitions belong at top level; actions belong under the guard.

how usedguarded version does
run directlyprints 212.0
importednothing (silent)

82. Fill in: guarded version does for Trap: work that runs at import time

Comparison

Comparison matrix

From Trap: work that runs at import time: refill the guarded version does column from what you know. The rest of the table is as it appeared.

how usedguarded version does
run directlyprints 212.0
importednothing (silent)

83. Something is wrong here: mismatched import form and call

Anomaly

Predict first

A student writes this, and it looks reasonable:

You import with from, then call as if you used plain import (or the reverse).

It is wrong. Say what breaks — and say it before you turn the page.

Correct: You brought in sqrt only; the name math was never created, so math.pi has nothing to look up.

Match the call to the import form you chose.

Why: You brought in sqrt only; the name math was never created, so math.pi has nothing to look up.

84. Trap: mismatched import form and call

Trap

The trap

You import with from, then call as if you used plain import (or the reverse).

from math import sqrt

print(math.pi)

from math import sqrt never defines math

Why: You brought in sqrt only; the name math was never created, so math.pi has nothing to look up.

lineresult
math.pi (after from-import)NameError: name 'math' is not defined. Did you forget to import 'math'?

The fix

Match the call to the import form you chose.

from math import sqrt, pi

print(pi)

Import what you call, call what you import

Why: Real error, verbatim: NameError: name 'math' is not defined. Did you forget to import 'math'? Either import pi too and call pi, or use import math and call math.pi. (The reverse - import math then bare sqrt(16) - gives NameError: name 'sqrt' is not defined.)

import formhow you must call
import mathmath.sqrt, math.pi
from math import sqrt, pisqrt, pi (no prefix)

85. Which of these survive contact with Session 21 - Modules, Packages & pip?

Two truths and a lie

Sort into buckets

Some of these hold up and some are the exact mistakes this lesson is built to prevent. Sort them.

Holds up
Python ships with a huge standard library - hundreds of modules you never have to install. math, random, datetime, statistics are all there, waiting.; import math runs the module once and gives you a name (math) to reach its contents. You do this once, near the top of your file.; math.sqrt reads left to right: 'in the math module, the sqrt name'. The dot keeps names organized so two modules can each have a sqrt without clashing.
Breaks
You import a package you never installed (or misspelled).; You name your own file random.py, then import random elsewhere in the same folder.
sound
These are stated as this lesson states them — each one survives the edge cases Session 21 - Modules, Packages & pip puts it through.
flawed
Each of these is lifted from a trap in this deck: reasonable-sounding, and wrong in a way that only shows up once you rely on it.

86. Patterns & Checks

Section

Part 10

87. Choosing an import form

Pattern

1. Use the tool occasionally? import module

Why: The module. prefix keeps the origin visible: math.sqrt tells the reader where sqrt lives.

2. Use one or two names constantly? from module import name

Why: Pulls just those names in for short, clean calls - at the cost of hiding where they came from.

3. Long or conventional name? import module as alias

Why: import numpy as np: shorter to type and matches what every reader expects.

4. Avoid from module import * in real code

Why: It hides origins and can silently overwrite your own names. Fine for a throwaway experiment only.

88. Turning a script into a reusable module

Pattern

1. Put your reusable defs at the top level

Why: Functions and classes should be importable without side effects.

2. Move demos/tests under if __name__ == "__main__":

Why: So importing the file borrows the functions silently, while running it still shows the demo.

3. Name the file something that does not collide

Why: Never random.py, math.py, etc. - your folder is searched first and would shadow the real module.

4. For third-party needs: venv + pip + requirements.txt

Why: Isolate the project, install what you need, and pin it so anyone can reproduce the setup.

89. Where this shows up: Session 21 - Modules, Packages & pip

Real world

Discussion prompt

Outside this lesson: where does Session 21 - Modules, Packages & pip actually turn up? Name one concrete situation — a job, a piece of software someone ships, a decision somebody has to make — and say which part of Turning a script into a reusable module is doing the work in it.

Hint: Vague is the failure mode here. "Engineering" is not a situation; "deciding whether this build is fast enough to ship" is.

Answer:

Session 21 of the Python Fundamentals series, in depth. Reusing code beyond a single file: importing from the standard library (import math, from math import sqrt, import x as np, from x import *), writing and importing your own module, the if __name__ == '__main__' guard, packages as folders of modules, installing third-party code with pip and requirements.txt, isolating projects with virtual environments, and the import search path.

90. Check: dot notation

Check

Predict the output.

import math

print(math.sqrt(64))
callprints
math.sqrt(64)?

Check your understanding

What does this print?

  • A. 8.0 (correct)
  • B. 8
  • C. 64
  • D. NameError: name 'sqrt' is not defined

Answer: A

Why: import math makes the module available; math.sqrt(64) returns 8.0 - sqrt always gives a float. Verified by execution.

Why B tempts people
sqrt returns a float, so it is 8.0, not the integer 8.
Why C tempts people
sqrt is the square root, not the number itself; 64 would be the input, not the result.
Why D tempts people
That error happens with a bare sqrt(64); here it is correctly reached through math.sqrt.

91. Check: from-import scope

Check

The import brings in sqrt only.

from math import sqrt

print(math.pi)
name importedname usedresult
sqrtmath?

Check your understanding

What happens?

  • A. NameError: name 'math' is not defined (correct)
  • B. It prints 3.141592653589793
  • C. It prints None
  • D. ModuleNotFoundError: No module named 'math'

Answer: A

Why: from math import sqrt creates the name sqrt but never the name math, so math.pi raises NameError: name 'math' is not defined. Verified by execution.

Why B tempts people
pi was not imported and math was never defined, so nothing prints - it errors first.
Why C tempts people
There is no value to print; the lookup of math fails before print runs.
Why D tempts people
math exists and imports fine; the problem is the name math was not bound in this file, which is a NameError, not ModuleNotFoundError.

92. Rule out three: Check: the guard when imported

Elimination

Eliminate the wrong options

When another file runs import this_module, what does the import print?

3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.

  • A. Nothing - the guarded block is skipped
  • B. 12
  • C. It raises an error because __name__ is undefined
  • D. It prints __main__

Survives elimination: A

Why: When imported, __name__ is the module's name, not "__main__", so the if is False and the print never runs - the import is silent. Verified by execution.

93. Check: the guard when imported

Check

This file is imported by another file, not run directly.

def area(w, h):
    return w * h

if __name__ == "__main__":
    print(area(3, 4))
how usedguard blockoutput
imported??

Check your understanding

When another file runs import this_module, what does the import print?

  • A. Nothing - the guarded block is skipped (correct)
  • B. 12
  • C. It raises an error because __name__ is undefined
  • D. It prints __main__

Answer: A

Why: When imported, __name__ is the module's name, not "__main__", so the if is False and the print never runs - the import is silent. Verified by execution.

Why B tempts people
12 prints only when the file is run directly; importing skips the guarded block.
Why C tempts people
__name__ is always defined by Python automatically; it is never missing.
Why D tempts people
When imported, __name__ is the module name, not __main__ - and nothing prints it here anyway.

94. Check: the shadowing trap

Check

You saved this file as random.py, in the same folder you run it from.

# file saved as random.py
import random

print(random.randint(1, 6))
file nameimport random loadsresult
random.py??

Check your understanding

What is the likely result?

  • A. AttributeError: module 'random' has no attribute 'randint' (correct)
  • B. A random integer from 1 to 6
  • C. ModuleNotFoundError: No module named 'random'
  • D. It always prints 1

Answer: A

Why: Your folder is searched first, so import random loads your own random.py, which has no randint, raising AttributeError: module 'random' has no attribute 'randint'. Verified by execution.

Why B tempts people
The stdlib random never loads - your same-named file shadows it - so randint is unavailable.
Why C tempts people
A module named random IS found (yours), so it is not a ModuleNotFoundError; it is an AttributeError.
Why D tempts people
randint is never reached at all; the attribute lookup fails before any number is produced.

95. Check: alias

Check

The module is renamed on import.

import math as m

print(m.factorial(4))
aliascallprints
mm.factorial(4)?

Check your understanding

What does this print?

  • A. 24 (correct)
  • B. 16
  • C. NameError: name 'math' is not defined
  • D. 10

Answer: A

Why: import math as m loads math under the name m, and factorial(4) is 4 x 3 x 2 x 1 = 24. Verified by execution.

Why B tempts people
16 is 4 squared (4 ** 2), not the factorial 4 x 3 x 2 x 1.
Why C tempts people
The alias replaces math with m; you would only get that error if you wrote math.factorial after aliasing.
Why D tempts people
10 is 4 + 3 + 2 + 1, a sum; factorial multiplies, giving 24.

96. Check: pip vs import

Check

You wrote pip install requests inside your Python file and ran it.

pip install requests
import requests
line 1belongs inresult
pip install requests??

Check your understanding

Why does line 1 fail as written?

  • A. pip install is a terminal command, not Python - it raises a SyntaxError in a .py file (correct)
  • B. requests is part of the standard library, so no install is needed
  • C. You must write import pip first
  • D. pip only works inside a virtual environment, never elsewhere

Answer: A

Why: pip install requests is a shell command you run in the terminal, not valid Python syntax; placing it in a .py file is a SyntaxError. Install in the terminal, then import in the file.

Why B tempts people
requests is a third-party package, not in the standard library - it must be installed with pip.
Why C tempts people
Importing pip does not turn a shell command into valid Python; the line is still not Python syntax.
Why D tempts people
pip works with or without a venv; venvs are recommended for isolation but are not what makes this line fail.

97. Connect it up: Session 21 - Modules, Packages & pip

Connect it up

Draw it

One page, no notation unless you need it: draw how these connect — The import Statement · from, as, and ** · Writing Your Own Module · The __name__ Guard · Packages: Folders of Modules · pip* & requirements. Put an arrow wherever one of them is what makes another possible, and label the arrow with why.

98. What you can do now

Recap

You can reach beyond a single file: import the standard library, split your own code into modules, group them into packages, and pull in third-party code with pip.

You writeIt means
import mathload the module; use math.sqrt
from math import sqrtbring in just sqrt (no prefix)
import numpy as npload numpy, call it np
if __name__ == "__main__":run this only when the file is run directly
python -m pip install Xinstall a package from PyPI (terminal)
python -m venv .venvmake an isolated environment for the project

Watch the two big traps: ModuleNotFoundError (install it, or fix the spelling / venv) and shadowing (never name a file random.py). Next session we build on this to structure a real multi-file project.

Sources

  1. Python 3 Tutorial - Modules
  2. Python 3 Tutorial - Packages
  3. Python 3 Library - venv (virtual environments)
  4. All snippets and error messages executed and copied from CPython 3.12. — Author verification run, 2026-07-15 (Python Fundamentals series, Session 21).

Want this taught 1-on-1? Alexander tutors Python Fundamentals — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108