Python Module 6 – Modules, packages and the standard library
Modules, imports and __name__
Split Python code into modules, import names in each form, guard script code with if __name__ == '__main__', and see how sys.path finds a module.
What you will learn
- Predict how often a module's code runs and which names each form of import binds
- Trace the folders of sys.path that Python searches for a module
- Fix a file of your own that hides a standard module
- Resolve a circular import by moving the shared name into its own module
- Write a script whose demo code runs only under if __name__ == '__main__'
Before you start
What you’ll learn · Before you start
What you will learn
- Predict how often a module's code runs and which names each form of import binds
- Trace the folders of sys.path that Python searches for a module
- Fix a file of your own that hides a standard module
- Resolve a circular import by moving the shared name into its own module
- Write a script whose demo code runs only under if __name__ == '__main__'
Before you start
On this page
One file stops being enough as a program grows: the word-counting functions of one script are soon wanted by a
second script and by a test. A module: A file of Python code, such as textstats.py, that other files use with import. The first import in a program runs the file once and keeps the module object it built, so every later import gets that same object. is a file of Python code, such as textstats.py, and
import textstats lets another file use what it defines; the program below imports it with three different
statements, and the file still runs only once.
The first import finds the file, runs it from top to bottom and keeps the module object it built, so every later
import gets that same object. Inside every module, the name __name__ says how the file is being used:
'__main__' in the file you started as the program, and the module’s own name when another file imports it, which is
how one file can be a reusable module and a runnable script at once. You will also trace where Python looks for a
module and fix two mistakes that break imports: a file that hides a standard module, and two modules that import each
other.
The first import runs the file, later imports reuse it
Here are two files from one folder. textstats.py defines three functions for counting words and has two lines of
code at the top level that print; main.py imports it with three different statements. Each tab runs its own file
as the program, with the other file beside it:
Python (main.py) · first/main.py
import sys
import textstats
import textstats as ts
from textstats import word_count
line = "Practice makes progress, and progress makes practice easier."
print(word_count(line), "words")
print("Most common:", textstats.most_common(line))
print("One module object?", ts is textstats is sys.modules["textstats"])
print("Its name when imported:", repr(textstats.__name__)) Output
textstats.py is running, and its __name__ is 'textstats' 8 words Most common: practice One module object? True Its name when imported: 'textstats'
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 main.py
Python (textstats.py) · first/textstats.py
"""Small helpers for counting the words of a text."""
PUNCTUATION = ".,;:!?\"'()"
def words(text):
"""The words of text in lower case, without punctuation at their ends."""
cleaned = (w.strip(PUNCTUATION).casefold() for w in text.split())
return [w for w in cleaned if w]
def word_count(text):
return len(words(text))
def most_common(text):
"""The word used most often (on a tie, the one that appears first)."""
counts = {}
for w in words(text):
counts[w] = counts.get(w, 0) + 1
return max(counts, key=counts.get)
print("textstats.py is running, and its __name__ is", repr(__name__))
if __name__ == "__main__":
# Runs only when this file is the program, never when it is imported.
sample = "The cat sat on the mat. The end."
print("Self-check:", word_count(sample), "words; most common:", most_common(sample)) Output
textstats.py is running, and its __name__ is '__main__' Self-check: 8 words; most common: the
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 textstats.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Three import statements, yet the line that textstats.py prints appears once. Step through what each statement did:
an arrow to sys.modules is the import asking the module cache: The dictionary sys.modules, which maps the name of every module a program has imported to its module object. An import looks there first, which is why a module's file runs only once per program., and the sub-labels
say what each part holds.
The drawing shows the whole scene; its 8 steps are listed below.
Every step as text
- The first import. Line 3, import textstats, is the first import of textstats in this program. Python asks sys.modules first, and textstats is not in it.
- Found and created. Python searches the folders of sys.path and finds textstats.py beside main.py. It creates an empty module object and puts it into sys.modules before it runs a single line of the file.
- The file runs once. Now the file runs from top to bottom. The three def statements create functions, and the print() at the top level runs: the first line of the output. __name__ is 'textstats', so the guarded self-check is skipped.
- The name is bound. The module is complete, and the import binds the name textstats in main.py to it. That is the whole of the first import statement.
- The second import. Line 4, import textstats as ts, asks sys.modules again. This time textstats is there, so Python neither searches the folders nor runs the file.
- Another name, same object. The import binds a second name, ts, to the module it found. Nothing is printed, and ts is textstats is True.
- One name from the module. Line 5, from textstats import word_count, also finds the module in sys.modules. It binds one name in main.py, word_count, to the function the module holds.
- What the run printed. The rest of main.py uses the three names. Its output has the line from textstats.py once, and One module object? True: three import statements, one module object, one run of its file.
The first import ran the file and stored the new module in the module cache, sys.modules, the dictionary of every
module the program has imported so far; the other two found it there and only
gave it another name. That is why ts is textstats is sys.modules["textstats"] is True: three names for one module
object. Press Edit, add a fourth import textstats at the end of main.py and run it: the line from textstats.py
still appears once.
Now look at the run of textstats.py itself. Its __name__ is '__main__' this time, and the self-check under
if __name__ == "__main__": runs. When main.py imported the same file, __name__ was 'textstats', so that block
was skipped while the print() above it still ran. Everything at a module’s top level runs on import: def
statements only create functions, which is harmless, but a call, a print or a file being read happens for every
program that imports the module. Keep that kind of code inside functions or under the guard.
Key idea
A module’s file runs once per program, on its first import; every later import, in any form and from any file,
gets the module object that sys.modules keeps.
Checkpoint
Check: One run per program, then the cache
Each form of import binds a different name
All five forms find and load the module the same way; they differ only in the names they bind in your file. With
textstats from the example:
| Statement | What it binds in your file | Then you write |
|---|---|---|
import textstats |
the module, as textstats |
textstats.word_count(line) |
import textstats as ts |
the module, as ts |
ts.word_count(line) |
from textstats import word_count |
one name of the module | word_count(line) |
from textstats import word_count as count |
that name, as count |
count(line) |
from textstats import * |
every public name of the module | names whose origin the reader cannot see |
import textstatskeeps the source visible at every use:textstats.word_counttells a reader where the function lives. It suits a module you use in many places.from textstats import word_countis shorter for a few names you use often. It copies the reference when the import runs, so if the module later binds that name to something new, your file keeps the old object.asgives a shorter or clearer name. Use it for long module names and for aliases that readers of your code already know.- A star import: from module import *, which binds every public name of a module in your file: the names in its __all__ list if it has one, otherwise every name that does not start with an underscore. It can silently replace names you already have.,
from module import *, binds every public name: the names listed in the module’s__all__if it has that list, otherwise every name that does not start with an underscore. It can silently replace names you already have, built-in ones included:
from os import * # every public name of os, including its own open()
print("open now comes from", open.__module__)
try:
with open("notes.txt") as notes: # meant the built-in open()
print(notes.read())
except TypeError as error:
print("TypeError:", error) Output
open now comes from posix TypeError: open() missing required argument 'flags' (pos 2)
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 star_import.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The os module has a low-level open() of its own, which needs flags such as os.O_RDONLY, and the star import put
it in the place of the built-in open(); the first line names posix, the module that os takes it from on Linux
and macOS. The error talks about a missing flags argument and says nothing about the import that caused it, which
is the real cost of *: the bug and its cause sit far apart. Keep star imports for quick experiments at the
interactive prompt.
Checkpoint
Check: What each form of import binds
Python takes the first match on sys.path
When a module is not in sys.modules yet, Python checks the modules built into the interpreter itself (their names
are in sys.builtin_module_names) and then searches the folders of the
module search path: The list sys.path of folders that Python searches, in order, for a module it has not imported yet. For a script it starts with the script's folder, then the standard library, then site-packages, and the first match wins., sys.path, in order, taking the first match: a file
textstats.py, a package folder textstats/ or a compiled extension module. This program says what each entry of
sys.path is for:
import os
import sys
script_folder = os.path.dirname(os.path.abspath(__file__))
def describe(entry):
"""What one folder of the module search path is for."""
if entry == "":
return "'' (empty): the current folder"
if entry == script_folder:
return "the folder of the script that is running"
if entry.endswith(".zip"):
state = "present" if os.path.exists(entry) else "absent here, so it is skipped"
return f"a zip file of the standard library ({state})"
if entry.endswith("lib-dynload"):
return "lib-dynload: standard modules compiled from C"
if entry.endswith("site-packages"):
return "site-packages: packages you install"
return "the standard library"
for number, entry in enumerate(sys.path, start=1):
print(number, describe(entry)) Output
1 the folder of the script that is running 2 a zip file of the standard library (absent here, so it is skipped) 3 the standard library 4 lib-dynload: standard modules compiled from C 5 site-packages: packages you install
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 where.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Version note
In the browser runner the first entry is an empty string, which means the current folder (where the page puts the files of the example), and the standard library is read from the zip file, which exists there. Run as a file on your computer, a script gets its own folder as the first entry instead.
In your browser, Pyodide 314.0.7 (CPython 3.14.2) prints:
1 '' (empty): the current folder 2 a zip file of the standard library (present) 3 lib-dynload: standard modules compiled from C 4 site-packages: packages you install
- First, the script’s folder. The first entry is the folder that holds the script you ran, or the current folder
for
python -m,python -cand the interactive prompt. That is howmain.pyfindstextstats.pynext to it. - Then the standard library. Python lists a zip file of the standard library even when there is none (it is
skipped), then the library’s folder and
lib-dynload, its modules compiled from C. - Last,
site-packages. That is wherepipinstalls packages: the environment’s own folder when a virtual environment is active. ThePYTHONPATHenvironment variable, when set, adds folders after the first entry.
The flowchart puts a whole import together: the cache first, then the search in this order, then one run of the file and the name bound in your file.
What Python does for import textstats
Text description of the diagram
The flowchart reads from top to bottom.
- It starts at the statement import textstats.
- First question: is textstats already in sys.modules, the cache of modules this program has imported? If yes, Python reuses that module object and goes straight to the last step.
- If not, Python searches in order: the built-in modules, then each folder listed in sys.path, which starts with the folder of your script, then the standard library, then site-packages.
- Second question: was a module of that name found? If not, the import raises ModuleNotFoundError.
- If it was found, Python creates a module object, adds it to sys.modules and runs the file once, from top to bottom.
- Last step: the name textstats is bound in the importing file, pointing at the module object.
Checkpoint
Check: The order of sys.path
A file named like a standard module hides it
Because the script’s folder comes first, a file of your own with the name of a standard module wins over it, and the
standard module becomes a shadowed module: A standard module that an import cannot reach because a file or package of yours with the same name comes earlier on sys.path, such as your own statistics.py next to the script that imports statistics.. Here a folder holds statistics.py,
written for one project’s marks, and report.py, which wants the standard library’s statistics.median:
Python · clash/report.py
import statistics
marks = [72, 85, 64, 91]
print("Median mark:", statistics.median(marks)) Output (exit status 1)
Printed as an error (standard error)
Traceback (most recent call last):
File "report.py", line 4, in <module>
print("Median mark:", statistics.median(marks))
^^^^^^^^^^^^^^^^^
AttributeError: module 'statistics' has no attribute 'median' (consider renaming 'statistics.py' since it has the same name as the standard library module named 'statistics' and prevents importing that standard library module)
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 report.py
clash/statistics.py
"""My own helpers for marks. Its name hides the standard library's statistics module."""
def average(marks):
return sum(marks) / len(marks) Step through the search that line 1 of report.py made, and watch where it stopped:
The drawing shows the whole scene; its 7 steps are listed below.
Every step as text
- The statement. report.py starts with import statistics. It means the standard module, but the import statement only knows a name, and Python looks for the first module of that name.
- Not cached yet. Nothing in this program has imported statistics yet, and Python does not load it while it starts, so sys.modules has no entry of that name and the search begins.
- Not built in. Python first checks the modules compiled into the interpreter itself, whose names are in sys.builtin_module_names. statistics is not one of them: the standard module is a file in the library folder.
- The first folder matches. The first folder on sys.path is the script's own folder, and it holds statistics.py, written for one project's marks. That is a match, and the search stops: the standard library's folder, further down the list, is never searched.
- The wrong file runs. Python creates the module statistics from the local file, puts it into sys.modules and runs it once. It defines average() and nothing else.
- The missing name. Line 4 asks the module for median, and this module has none, so the program stops with AttributeError: module 'statistics' has no attribute 'median'. Python 3.13 and newer add a hint that names the file to rename.
- The fix. Rename the local file after its job, such as mark_stats.py. The script's folder then has no statistics.py, the search goes on to the standard library, and statistics.median() is the real one.
import statistics found the local file first, and it has no median. Python 3.13 and newer notice the clash and
name the file to rename, as the end of the message shows; the lesson on tracebacks
meets the same mistake with random.py. Older versions only say that the attribute is missing. Three habits prevent
it:
- Name files after what they do in your project (
mark_stats.py,dice_game.py), not after a general topic. - Check a name against
sys.stdlib_module_names, the set of every standard module name. The program below checks a whole folder:
"""List the files in this folder that would hide a module of the standard library."""
import sys
from pathlib import Path
for path in sorted(Path(".").iterdir()):
if path.suffix == ".py":
name = path.stem
elif path.is_dir():
name = path.name
else:
continue
if name in sys.stdlib_module_names:
print(f"{path.name} hides the standard library module {name!r}")
print(len(sys.stdlib_module_names), "names belong to the standard library") Output
statistics.py hides the standard library module 'statistics' 297 names belong to the standard library
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 check_names.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
- Run a single-file tool with
python -P, which leaves the script’s folder offsys.path:python -P report.pygets the realstatistics. The catch is that the script can no longer import other files beside it, so this suits standalone tools better than projects.
Not every standard module can be hidden this way. Python imports sys, os and a few others while it starts, before
your first line runs, and an import that finds a module in sys.modules never searches the folders. A local os.py
is therefore ignored, while a local statistics.py, json.py or random.py takes over.
Checkpoint
Check: A file that hides a standard module
Two modules that import each other meet half-built
A circular import: Two or more modules that import each other. It fails when one of them asks for a name that the other has not defined yet, because that module is still running its first lines. happens when two modules each need a name from the other while
they are still loading. orders.py needs discount_for from discounts.py, and discounts.py needs TAX_RATE from
orders.py:
Python · circular/main.py
import orders
print("Total:", orders.total(1500)) Output (exit status 1)
Printed as an error (standard error)
Traceback (most recent call last):
File "main.py", line 1, in <module>
import orders
File "orders.py", line 1, in <module>
from discounts import discount_for
File "discounts.py", line 1, in <module>
from orders import TAX_RATE
ImportError: cannot import name 'TAX_RATE' from 'orders' (consider renaming 'orders.py' if it has the same name as a library you intended to import)
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 main.py
circular/orders.py
from discounts import discount_for
TAX_RATE = 0.18
def total(price):
"""The price after the discount, with tax added."""
return round((price - discount_for(price)) * (1 + TAX_RATE), 2) circular/discounts.py
from orders import TAX_RATE
def discount_for(price):
"""10 % off when the price with tax is over 1,000."""
return price * 0.10 if price * (1 + TAX_RATE) > 1000 else 0 The traceback lists three first lines, one under the other. Step through them in the order they ran, and watch how
far orders.py has got when discounts.py asks it for TAX_RATE:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- main.py imports orders. Line 1 of main.py is import orders. Python finds orders.py, creates the module orders, adds it to sys.modules and starts running the file.
- orders needs discounts. Line 1 of orders.py is from discounts import discount_for. discounts is not loaded yet, so Python creates it, adds it to sys.modules and starts discounts.py, while orders waits on its first line.
- discounts needs orders. Line 1 of discounts.py is from orders import TAX_RATE. orders is already in sys.modules, so Python does not load it a second time. It looks for TAX_RATE in the module that exists.
- The name is not there yet. orders.py is still on line 1, waiting for discounts, and TAX_RATE = 0.18 is on line 3, which has not run. The module exists, but the name does not.
- ImportError. So the from import fails with ImportError: cannot import name 'TAX_RATE' from 'orders'. The traceback lists the three first lines, one under the other, which is the sign of a cycle.
- The fixed version. In the fixed files TAX_RATE lives in rates.py, which imports nothing of the project. discounts and orders both import it from there, every file loads to its end, and main.py prints Total: 1593.0 and Total: 944.0.
Read the traceback from the top: main.py imports orders, whose first line imports discounts, whose first line
asks orders for TAX_RATE. But orders.py has not got that far. It is still on its first line, waiting for
discounts to finish. The module object already exists, because Python adds a module to sys.modules just before
it runs the file, but TAX_RATE is not defined in it yet, so the import fails.
The message does not help much here. For a module in the script’s own folder, Python 3.13 and newer add the hint about renaming a file that hides a library, which is not the problem. Before Python 3.13 the same failure mentioned a “partially initialized module” and a likely circular import, and for modules inside a package it still does. When a name that plainly exists cannot be imported, look for a cycle. There are three ways out:
- Move what both need into a third module.
TAX_RATEgoes intorates.py, which imports neither of the others, and the cycle is gone. This is usually the best fix, because the shared value had no clear home before. - Import the module instead of the name. With
import ordersat the top ofdiscounts.pyandorders.TAX_RATEinside the function, the name is looked up when the function runs, long after both files have loaded. - Import inside the function that needs the name. It works, but it hides a dependency at the place nobody looks.
Python · fixed/main.py
import orders
print("Total:", orders.total(1500))
print("Total:", orders.total(800)) Output
Total: 1593.0 Total: 944.0
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 main.py
fixed/rates.py
TAX_RATE = 0.18 fixed/orders.py
from discounts import discount_for
from rates import TAX_RATE
def total(price):
"""The price after the discount, with tax added."""
return round((price - discount_for(price)) * (1 + TAX_RATE), 2) fixed/discounts.py
from rates import TAX_RATE
def discount_for(price):
"""10 % off when the price with tax is over 1,000."""
return price * 0.10 if price * (1 + TAX_RATE) > 1000 else 0 Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
With TAX_RATE in rates.py, every file loads to its end and both totals print: 1593.0 is the 1,500 order with
10 % off and 18 % tax added, and 944.0 is the 800 order with tax and no discount, because it stays under 1,000.
Checkpoint
Check: Modules that import each other
The guard keeps a script quiet when it is imported
The main guard: The block under if __name__ == "__main__": in a file. It runs when the file is started as the program and is skipped when the file is imported, because __name__ then holds the module's own name., if __name__ == "__main__":, matters whenever something imports your
file without you typing import yourself. Test runners import modules to call their functions. And
multiprocessing runs your main file again in a new Python process, under the name '__mp_main__', so that the code
there can find your functions. Its spawn start method, the default on Windows and macOS, does that in every worker it
starts; forkserver, the default on Linux since Python 3.14, does it once, in the server process from which it forks
the workers. This example uses spawn:
import multiprocessing as mp
print(f"{mp.current_process().name} runs this file, __name__ = {__name__!r}", flush=True)
def square(n):
print(f"{mp.current_process().name} works out {n} * {n} = {n * n}", flush=True)
if __name__ == "__main__":
# "spawn" starts each worker as a fresh Python that imports this file first
# (the default on Windows and macOS).
ctx = mp.get_context("spawn")
worker = ctx.Process(target=square, args=(7,))
worker.start()
worker.join()
print("The worker finished with exit code", worker.exitcode) Output
MainProcess runs this file, __name__ = '__main__' SpawnProcess-1 runs this file, __name__ = '__mp_main__' SpawnProcess-1 works out 7 * 7 = 49 The worker finished with exit code 0
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 workers.py
Four lines, from two processes. Step through the run process by process, and watch the guard give a different answer in each:
The drawing shows the whole scene; its 7 steps are listed below.
Every step as text
- The program starts. You run python workers.py. The file is the program, so its __name__ is '__main__', and its top-level print() gives the first line of the output.
- The guard is true. Python reaches if __name__ == "__main__":, which is true here. The block chooses the spawn start method and starts one worker process to run square(7).
- A fresh Python. Spawn starts a new Python interpreter. To call square() it needs the function, so it runs workers.py again from the top, this time under the name '__mp_main__'.
- The top level runs again. The worker's run of the top level prints the second line of the output, with its own process name, SpawnProcess-1, and __name__ = '__mp_main__'.
- The guard is false. In the worker, __name__ is not '__main__', so the guarded block is skipped and the worker starts no worker of its own. Without the guard it would try, and multiprocessing would stop it with a RuntimeError.
- The work itself. Only now does the worker call square(7), which prints the third line: SpawnProcess-1 works out 7 * 7 = 49.
- Back in the main process. The worker ends with exit code 0. worker.join() returns in the main process, which prints the last line: The worker finished with exit code 0.
The worker ran the top level of workers.py again, as its first line of output shows, but its __name__ was
'__mp_main__', so the guard kept it from starting a worker of its own. Without the guard, every new process would
try to start more processes while it was still importing the file, and multiprocessing stops that with a
RuntimeError, as its documentation explains under “Safe importing of main module”.
Keep the guarded block short. Put the program’s work in a function, usually called main(), and make the guard a
single line, sys.exit(main()): main() returns 0 for success or another number for failure, and sys.exit()
hands it to the shell as the exit status. The exercise below turns a script that prints a report on import into one
that does this.
Version note
Python 3.15 adds explicit lazy imports (PEP 810): lazy import json binds the name at once but runs the module
only when the name is first used. Programs that import many modules start faster with it. The module of this track
on Python 3.15 covers it.
Checkpoint
Check: __name__ and the main guard
Practise: make a script safe to import
tally.py counts the votes of a class election, and importing it prints a whole report. Turn it into a module that
stays quiet when it is imported and a script that still prints the report, with a main() function and
sys.exit(main()) under the guard. The sample tests import your file and also run it as a program.
Exercise · Easy · Python
Make a script safe to import
tally.py counts the votes of a class election. Its functions are useful elsewhere, but importing the file also prints a whole report, because the demo code sits at the top level. Change the file so that it works both ways:
- Importing
tallyprints nothing. - A function
main(votes=None)prints the report and returns an exit status. With no argument it counts the demo votes already in the file. The report has one line per name in alphabetical order, such asAsha: 3, then a lineWinner: Asha, andmainreturns0. - When no vote has a name in it (every vote is empty or only spaces),
mainprintsNo votesand returns1. - Running the file as a program (
python tally.py) prints the report for the demo votes and exits with the status thatmainreturns: call it assys.exit(main())inside anif __name__ == "__main__":block.
Keep count_votes and winner as they are. The sample tests import your file as tally.
Starter code · tally.py
"""Count the votes of a class election and print the result."""
DEMO_VOTES = ["asha", "Ravi", " asha ", "meera", "ravi", "Asha", " "]
def count_votes(votes):
"""A dict of name -> number of votes; names are tidied with strip() and title(), empty votes are skipped."""
counts = {}
for vote in votes:
name = vote.strip().title()
if name:
counts[name] = counts.get(name, 0) + 1
return counts
def winner(counts):
"""The name with the most votes (on a tie, the name that comes first alphabetically)."""
return min(counts, key=lambda name: (-counts[name], name))
# Demo: it runs every time the file is imported, too.
counts = count_votes(DEMO_VOTES)
for name in sorted(counts):
print(f"{name}: {counts[name]}")
print("Winner:", winner(counts)) The sample tests · test_tally.py
import contextlib
import importlib
import io
import runpy
import tally
def output_of(action):
"""What action() prints, and what it returns."""
buffer = io.StringIO()
with contextlib.redirect_stdout(buffer):
result = action()
return buffer.getvalue(), result
def test_import_is_quiet():
"""importing tally prints nothing"""
printed, _ = output_of(lambda: importlib.reload(tally))
assert printed == ""
def test_count_votes():
"""count_votes still tidies the names and skips empty votes"""
assert tally.count_votes(["ravi", " RAVI", "", "meera"]) == {"Ravi": 2, "Meera": 1}
def test_main_report():
"""main(votes) prints one line per name, then the winner, and returns 0"""
printed, status = output_of(lambda: tally.main(["meera", "Ravi", "ravi"]))
assert printed == "Meera: 1\nRavi: 2\nWinner: Ravi\n"
assert status == 0
def test_main_tie():
"""on a tie the winner comes first alphabetically"""
printed, status = output_of(lambda: tally.main(["zoya", "Arjun"]))
assert printed == "Arjun: 1\nZoya: 1\nWinner: Arjun\n"
assert status == 0
def test_main_no_votes():
"""with no named vote, main prints No votes and returns 1"""
printed, status = output_of(lambda: tally.main(["", " "]))
assert printed == "No votes\n"
assert status == 1
def test_run_as_program():
"""python tally.py prints the demo report and exits with status 0"""
buffer = io.StringIO()
code = None
with contextlib.redirect_stdout(buffer):
try:
runpy.run_path("tally.py", run_name="__main__")
except SystemExit as stop:
code = stop.code
assert buffer.getvalue() == "Asha: 3\nMeera: 1\nRavi: 2\nWinner: Asha\n"
assert code == 0 A hint
Move the four demo lines into the body of def main(votes=None): and replace the demo list with votes when it is not None. Check if not counts: before you look for a winner, because winner() cannot pick from an empty dictionary. Then add the guard as the last lines of the file, with import sys at the top.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
The self-check below covers the six ideas of this lesson, from the module cache to the main guard. It starts with the idea your answers show you know least, and every choice says why it is right or what it gets wrong:
Practice that adapts to you
Practise this lesson
16 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Fresher · What does if __name__ == "__main__": do, and when does a script need it?
Every module has its name in __name__. Python sets it to "__main__" in the file you start as the program, and to
the module’s own name, such as "textstats", when the file is imported, so the guarded block runs only when the file
is the program. A script needs the guard as soon as anything else may import it: a test that calls its functions,
another module that reuses them, or multiprocessing, whose spawn and forkserver start methods (the defaults on every
platform since Python 3.14) import the main file again in a new process. Without the guard, that import would run
the demo code or try to start more processes, which multiprocessing refuses with a RuntimeError. The usual shape
is a main() function that does the work and sys.exit(main()) as the only line under the guard.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
A report script works on your laptop, but on the build server the same commit fails with "AttributeError: module 'statistics' has no attribute 'median'". Nobody changed the script. Explain the likely cause, how you would prove it in a minute, and how you would stop it from happening again.
-
Mid to senior
In a service package, billing.py and customers.py import each other, and it has worked for months. After someone sorts the imports in main.py, start-up fails with "cannot import name 'Invoice' from partially initialized module 'service.billing'". Explain why, which fixes you would consider, and which one you would choose.
Key takeaways
- A module is a
.pyfile that Python runs once per program, on the first import, and caches insys.modules, so every later import reuses the same module object. import mkeeps names qualified (m.f),from m import fbinds one name,asrenames, andfrom m import *can replace your names and built-ins, asfrom os import *does toopen().sys.pathstarts with the script’s folder, then the standard library, thensite-packages, and Python takes the first match.- A
statistics.pyorjson.pyof your own therefore hides the standard module: name files after their job and check names againstsys.stdlib_module_names. - A circular import fails when two modules need each other’s names while loading, so move the shared part into a third module, or import the module instead of the name.
__name__is'__main__'in the program you started and the module’s name when imported, so put demo code inmain()and call it underif __name__ == "__main__":.
Terms in this lesson
- circular import
- Two or more modules that import each other. It fails when one of them asks for a name that the other has not defined yet, because that module is still running its first lines.
- main guard
- The block under if __name__ == "__main__": in a file. It runs when the file is started as the program and is skipped when the file is imported, because __name__ then holds the module's own name.
- module
- A file of Python code, such as textstats.py, that other files use with import. The first import in a program runs the file once and keeps the module object it built, so every later import gets that same object.
- module cache
- The dictionary sys.modules, which maps the name of every module a program has imported to its module object. An import looks there first, which is why a module's file runs only once per program.
- module search path
- The list sys.path of folders that Python searches, in order, for a module it has not imported yet. For a script it starts with the script's folder, then the standard library, then site-packages, and the first match wins.
- shadowed module
- A standard module that an import cannot reach because a file or package of yours with the same name comes earlier on sys.path, such as your own statistics.py next to the script that imports statistics.
- star import
- from module import *, which binds every public name of a module in your file: the names in its __all__ list if it has one, otherwise every name that does not start with an underscore. It can silently replace names you already have.
References
- The Python Tutorial: Modules (Python Software Foundation)
- The import system (The Python Language Reference) (Python Software Foundation)
- The import statement (The Python Language Reference) (Python Software Foundation)
- __main__, top-level code environment (Python Software Foundation)
- The initialization of the sys.path module search path (Python Software Foundation)
- sys.stdlib_module_names (The Python Standard Library) (Python Software Foundation)
- Command line and environment: the -P option (Python Software Foundation)
- multiprocessing: the spawn and forkserver start methods (Python Software Foundation)
- PEP 810: Explicit lazy imports (Python Software Foundation)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress