Quiz: Modules, Packages, and the Python Ecosystem
Test your understanding before moving on. Target: 70% or higher to proceed confidently.
Section 1: Multiple Choice (1 point each)
1. What is a Python module?
- A) A directory containing multiple Python files
- B) A single
.pyfile that can be imported - C) A function that belongs to a class
- D) A virtual environment for managing packages
Answer
**B)** A single `.py` file that can be imported. *Why B:* A module is simply a `.py` file. Any Python file is a module that can be imported by other Python files. *Why not A:* That describes a package, not a module. *Why not C:* That describes a method, not a module. *Why not D:* Virtual environments are separate from the concept of modules. *Reference:* Section 12.12. After executing from math import sqrt, pi, which of the following will raise a NameError?
- A)
sqrt(16) - B)
pi - C)
math.ceil(4.2) - D) Both A and B will work fine
Answer
**C)** `math.ceil(4.2)` *Why C:* `from math import sqrt, pi` imports only `sqrt` and `pi` into the namespace. The name `math` itself is not defined, so `math.ceil()` raises `NameError`. *Why not A:* `sqrt` was explicitly imported. *Why not B:* `pi` was explicitly imported. *Why not D:* While A and B both work, C does not — the question asks which will fail. *Reference:* Section 12.23. What is the community-standard alias for importing datetime?
- A)
import datetime as date - B)
import datetime as dt - C)
import datetime as d - D) There is no standard alias for datetime
Answer
**B)** `import datetime as dt` *Why B:* `dt` is the widely recognized community convention for aliasing the `datetime` module, similar to `np` for numpy and `pd` for pandas. *Why not A:* `date` could be confused with `datetime.date`. *Why not C:* `d` is too short and not conventional. *Why not D:* There is a standard alias (`dt`), though it's less universally enforced than `np` or `pd`. *Reference:* Section 12.24. Why is from os import * considered bad practice?
- A) It's a syntax error in Python 3.12
- B) It imports hundreds of names into your namespace, making it hard to track where names come from
- C) It only works on Linux, not Windows
- D) It makes your program slower because it loads the entire os module
Answer
**B)** It imports hundreds of names into your namespace, making it hard to track where names come from. *Why B:* `import *` dumps all public names from the module into your namespace. You can't tell where functions came from, and two `import *` statements can silently overwrite each other's names. *Why not A:* It's valid syntax — it works, it's just bad practice. *Why not C:* It works on all platforms. *Why not D:* Python loads the entire module regardless of import style; the performance difference is negligible. *Reference:* Section 12.25. How many times does Python execute a module's code when it's imported multiple times in the same program?
- A) Once per import statement
- B) Exactly once, on the first import
- C) It depends on whether you use
importorfrom...import - D) Zero — Python only loads functions, not executable code
Answer
**B)** Exactly once, on the first import. *Why B:* Python caches imported modules. The first `import` executes the file and creates a module object. Subsequent imports reuse the cached object. *Why not A:* Re-importing does not re-execute the module. *Why not C:* Both styles trigger loading once and only once. *Why not D:* Python does execute top-level code (assignments, print statements, etc.) on the first import. *Reference:* Section 12.26. What does collections.Counter do?
- A) Counts the number of elements in a list
- B) Creates a dict subclass that maps each element to its count
- C) Counts from one number to another, like
range() - D) Tracks how many times a function has been called
Answer
**B)** Creates a dict subclass that maps each element to its count. *Why B:* `Counter` takes an iterable and produces a dictionary-like object where keys are elements and values are their counts. It also provides convenience methods like `.most_common()`. *Why not A:* That's just `len()`. *Why not C:* That's `range()` or `itertools.count()`. *Why not D:* That would be a decorator or closure pattern, not `Counter`. *Reference:* Section 12.37. What is the purpose of the file __init__.py in a directory?
- A) It initializes Python variables when the program starts
- B) It tells Python that the directory should be treated as a package
- C) It must contain the
main()function for the package - D) It lists all files that belong to the package
Answer
**B)** It tells Python that the directory should be treated as a package. *Why B:* Without `__init__.py`, Python treats the directory as just a folder and won't allow importing from it. `__init__.py` can be empty or contain initialization code. *Why not A:* It doesn't initialize Python — it identifies a package. *Why not C:* It doesn't need to contain `main()` — any code or no code is valid. *Why not D:* Python discovers files automatically; `__init__.py` doesn't list them. *Reference:* Section 12.68. When you run python grading.py, what is the value of __name__ inside grading.py?
- A)
"grading" - B)
"__main__" - C)
"grading.py" - D)
None
Answer
**B)** `"__main__"` *Why B:* When a file is run directly (as the entry point), Python sets its `__name__` to `"__main__"`. This is why the guard `if __name__ == "__main__":` works. *Why not A:* `"grading"` would be the value if the file were imported, not run directly. *Why not C:* The `.py` extension is not included. *Why not D:* `__name__` is always a string, never `None`. *Reference:* Section 12.59. You have a file called math.py in your project directory. What happens when another file in the same directory runs import math?
- A) Python imports the standard library
mathmodule - B) Python imports your
math.pyfile instead of the standard library module - C) Python raises an
ImportErrorbecause of the name conflict - D) Python imports both and merges them
Answer
**B)** Python imports your `math.py` file instead of the standard library module. *Why B:* The script's directory is first in `sys.path`, so Python finds your `math.py` before the standard library's `math`. This is called "name shadowing." *Why not A:* The standard library is checked after the local directory. *Why not C:* Python doesn't detect name conflicts — it just uses the first match. *Why not D:* Python never merges modules. *Reference:* Section 12.910. What command installs the requests package from PyPI?
- A)
python install requests - B)
pip install requests - C)
import requests --install - D)
pip download requests
Answer
**B)** `pip install requests` *Why B:* `pip install` is the standard command for installing packages from PyPI. *Why not A:* `python install` is not a valid command. *Why not C:* Import statements don't have command-line flags. *Why not D:* `pip download` downloads without installing. *Reference:* Section 12.7Section 2: True/False with Justification (1 point each)
11. "Every Python file you create is automatically a module."
Answer
**True** *Explanation:* Any `.py` file is a module. You can import it by name (without the `.py` extension) as long as Python can find it on the module search path. You don't need to add any special declaration to make a file importable. *Reference:* Section 12.112. "The __name__ guard prevents a module from being imported."
Answer
**False** *Explanation:* The `__name__` guard does not prevent importing. It controls which code runs *when* the file is imported versus run directly. The functions and classes in the file are still importable. Only the code inside the `if __name__ == "__main__":` block is skipped on import. *Reference:* Section 12.513. "You can install standard library modules using pip install."
Answer
**False** *Explanation:* Standard library modules (like `math`, `datetime`, `random`, `os`) ship with Python and are always available. You don't need to install them. `pip install` is for third-party packages from PyPI that are not included with Python. *Reference:* Sections 12.3, 12.714. "If two modules each import the other, Python will always raise an ImportError."
Answer
**False** *Explanation:* Circular imports don't always fail — the outcome depends on the specific code structure and what names each module tries to use from the other at import time. However, they frequently cause `ImportError` or `AttributeError` and are considered a code smell. Restructuring to remove the cycle is the best practice. *Reference:* Section 12.9Section 3: Short Answer (2 points each)
15. Complete the following table. Fill in the "Access Pattern" and "Potential Risk" for each import style.
| Import Statement | Access Pattern | Potential Risk |
|---|---|---|
import random |
? | ? |
from random import randint |
? | ? |
from random import * |
? | ? |
Answer
| Import Statement | Access Pattern | Potential Risk | |---|---|---| | `import random` | `random.randint(1, 10)` | Verbose, but no risk of name collision | | `from random import randint` | `randint(1, 10)` | Could shadow a local `randint` name | | `from random import *` | `randint(1, 10)` | Imports ALL names; can silently overwrite local names; unclear where names come from | *Rubric — full credit requires:* - Correct access pattern for each style - Identifying the risk level escalation from safe to dangerous *Reference:* Section 12.216. Explain what sys.path is and why the order of directories in it matters. Describe one specific scenario where the order causes a bug.
Answer
`sys.path` is a list of directory paths that Python searches, in order, to find modules when you use `import`. The order matters because Python uses the first match it finds and stops searching. **Bug scenario:** If you create a file called `random.py` in your project directory, Python finds it before the standard library's `random` module (because the project directory is typically first in `sys.path`). Any attempt to use `random.randint()` or other standard library `random` functions will fail with an `AttributeError` because Python loaded your file instead. *Rubric — full credit requires:* - Defining `sys.path` as the search path list - Explaining first-match-wins behavior - Providing a concrete shadowing example *Reference:* Section 12.817. You are building a project with this structure:
inventory/
__init__.py
products.py
orders.py
reports.py
Write the import statements you would use in reports.py to import the Product class from products.py and the get_pending_orders() function from orders.py. Use relative imports.
Answer
from .products import Product
from .orders import get_pending_orders
The `.` indicates a relative import from the same package. This is the preferred style when one module inside a package imports from another module in the same package.
*Rubric — full credit requires:*
- Using relative import syntax (`.` prefix)
- Correct module and name references
*Reference:* Section 12.6
Section 4: Applied Scenario (3 points each)
18. Elena has a project with this structure:
reports/
main.py
data_loader.py
analyzer.py
formatter.py
datetime.py <-- !!!
She runs main.py and gets this error:
AttributeError: module 'datetime' has no attribute 'datetime'
a) What caused this error? b) How should she fix it? c) What general rule does this illustrate?
Answer
a) Elena has a file called `datetime.py` in her project directory. When any file in the project does `import datetime`, Python finds her `datetime.py` first (because the script's directory is searched before the standard library). Her file doesn't have a `datetime` class, causing the `AttributeError`. b) Rename `datetime.py` to something that doesn't clash — e.g., `date_utils.py` or `time_helpers.py`. Also delete any cached `__pycache__/datetime.cpython-*.pyc` file that might have been created. c) Never name your files after standard library modules. Python's module search path checks the local directory first, so local files shadow standard library modules of the same name. *Rubric:* | Criterion | 0 pts | 1 pt | 2 pts | 3 pts | |-----------|-------|------|-------|-------| | Diagnosis | Not attempted | Vaguely identifies naming issue | Correctly identifies name shadowing | Full explanation with sys.path reasoning | | Fix | Not attempted | Incomplete fix | Correct rename | Rename + cache cleanup + general rule |19. Dr. Patel has this DNA analysis code in one large file (dna_analysis.py, 600 lines). Propose a modular structure: list the modules you'd create, what functions each would contain, and write the main.py that imports and uses them. The code currently contains:
- Functions for reading FASTA files
- Functions for counting nucleotides and finding patterns
- Functions for displaying results in tables
- Functions for exporting results to CSV
Answer
**Proposed structure:**dna_project/
main.py
file_io.py # read_fasta(), export_to_csv()
analysis.py # count_nucleotides(), find_pattern(), gc_content()
display.py # print_summary_table(), print_pattern_matches()
**`main.py`:**
import file_io
import analysis
import display
def main():
sequences = file_io.read_fasta("sequences.fasta")
for name, seq in sequences.items():
counts = analysis.count_nucleotides(seq)
gc = analysis.gc_content(seq)
display.print_summary_table(name, counts, gc)
pattern = "ATCG"
matches = analysis.find_pattern(sequences, pattern)
display.print_pattern_matches(pattern, matches)
file_io.export_to_csv(sequences, "results.csv")
if __name__ == "__main__":
main()
*Rubric — full credit requires:*
- At least 3 modules with clear, non-overlapping responsibilities
- Logical grouping of functions into modules
- A `main.py` that imports and orchestrates
- `__name__` guard on `main.py`
20. Write a complete, working program that uses at least three different standard library modules to create a "daily briefing" script. The script should: - Display the current date and time (formatted nicely) - Generate a random motivational quote from a list - Show the current working directory and count of Python files in it
Answer
from datetime import datetime
import random
from pathlib import Path
def daily_briefing():
# Date and time
now = datetime.now()
print(f"{'=' * 40}")
print(f" Daily Briefing")
print(f" {now.strftime('%A, %B %d, %Y')}")
print(f" {now.strftime('%I:%M %p')}")
print(f"{'=' * 40}")
# Random quote
quotes = [
"The best time to plant a tree was 20 years ago. The second best time is now.",
"Code is like humor. When you have to explain it, it's bad.",
"First, solve the problem. Then, write the code.",
"Simplicity is the soul of efficiency.",
"Every expert was once a beginner.",
]
print(f"\n Quote of the day:")
print(f" \"{random.choice(quotes)}\"")
# Directory info
cwd = Path(".")
py_files = list(cwd.glob("*.py"))
print(f"\n Working directory: {cwd.resolve()}")
print(f" Python files here: {len(py_files)}")
for f in py_files:
print(f" - {f.name}")
if __name__ == "__main__":
daily_briefing()
*Rubric — full credit requires:*
- Uses at least 3 standard library modules
- Code runs without errors on Python 3.12+
- Output is formatted and readable
- Includes `__name__` guard
Scoring & Next Steps
| Score | Assessment | Recommended Action |
|---|---|---|
| < 50% | Needs review | Re-read sections 12.1-12.5, redo Part A exercises |
| 50-70% | Partial | Review weak areas, redo Part B exercises |
| 70-85% | Solid | Ready to proceed; revisit any missed topics |
| > 85% | Strong | Proceed; consider the extension exercises in Part E |