Exercises: Modules, Packages, and the Python Ecosystem

These exercises progress from basic import mechanics to designing multi-module projects. All code should run on Python 3.12+.

Difficulty Guide: - ⭐ Foundational (5-10 min each) - ⭐⭐ Intermediate (10-20 min each) - ⭐⭐⭐ Challenging (20-40 min each) - ⭐⭐⭐⭐ Advanced/Research (40+ min each)


Part A: Import Mechanics ⭐

A.1. Write three different import statements that would let you use the sqrt function from the math module. Show how you'd call sqrt(16) with each one.

A.2. What is wrong with each of the following import statements? Fix each one.

# a)
import math.sqrt

# b)
from random import *
print(randint(1, 10))

# c)
import datetime
print(datetime.now())

A.3. You write a program with this code at the top:

from math import pi
pi = 3.14

What value does pi hold after both lines execute? Why is this a problem?

A.4. Predict the output of this program:

import math as m
print(type(m))
print(m.__name__)
print(hasattr(m, 'sqrt'))

A.5. Explain the difference between a module and a package. Give an example of each from the Python standard library.

A.6. You have a file called string.py in your project directory with this code:

import string
print(string.ascii_uppercase)

What happens when you run it? Why? How do you fix it?


Part B: Standard Library Exploration ⭐⭐

B.1. Write a program that uses datetime to: - Print today's date in the format "Monday, March 14, 2025" - Calculate how many days until New Year's Day (January 1 of the next year) - Create a timestamp string suitable for a filename (e.g., report_2025-03-14_1430.txt)

B.2. Write a function roll_dice(num_dice, sides) using the random module that simulates rolling num_dice dice, each with sides sides. The function should return a tuple: (individual_rolls, total). Test it with 3 six-sided dice and 2 twenty-sided dice.

Expected output format:

Rolling 3d6: [4, 2, 6] = 12
Rolling 2d20: [17, 3] = 20

B.3. Use collections.Counter to solve this problem: given a string of text, find the 5 most common words. Ignore case. Test with this text:

text = """To be or not to be that is the question
Whether tis nobler in the mind to suffer
The slings and arrows of outrageous fortune
Or to take arms against a sea of troubles"""

B.4. Use pathlib.Path to write a function summarize_directory(directory) that prints: - The number of files (not directories) in the given directory - The number of .py files - The total size of all files in bytes - The most recently modified file

Test it on any directory on your system.

B.5. Use collections.defaultdict to group a list of words by their first letter. Given:

words = ["apple", "banana", "avocado", "blueberry", "cherry",
         "apricot", "cantaloupe", "blackberry"]

Expected output (dict with first letter as key, list of words as value):

{'a': ['apple', 'avocado', 'apricot'], 'b': ['banana', 'blueberry', 'blackberry'], 'c': ['cherry', 'cantaloupe']}

Part C: Creating Modules ⭐⭐

C.1. Create a module called temperature.py with three functions: - celsius_to_fahrenheit(c) — returns the Fahrenheit equivalent - fahrenheit_to_celsius(f) — returns the Celsius equivalent - celsius_to_kelvin(c) — returns the Kelvin equivalent

Include a __name__ == "__main__" guard that tests all three functions with sample inputs and prints the results.

Then create a separate file weather_report.py that imports from temperature and prints a formatted weather report for three cities with temperatures in all three scales.

C.2. Create a module called text_stats.py with these functions: - word_count(text) — returns the number of words - char_count(text, include_spaces=True) — returns character count - sentence_count(text) — returns number of sentences (count ., !, ?) - average_word_length(text) — returns average word length - reading_time(text, wpm=200) — returns estimated reading time in minutes

Include a __name__ guard that tests with a sample paragraph. Each function should handle the empty string case.

C.3. You have a file helpers.py with a function connect_to_database() at the top level (not inside any function). Explain why importing this module (import helpers) might cause problems even if you never call connect_to_database() yourself. How would you restructure the module?

C.4. Create two modules, validators.py and formatters.py, for processing student records:

validators.py should contain: - is_valid_grade(grade) — checks if grade is a letter A-F - is_valid_gpa(gpa) — checks if GPA is between 0.0 and 4.0 - is_valid_email(email) — checks if email contains @ and .

formatters.py should contain: - format_name(first, last) — returns "Last, First" - format_gpa(gpa) — returns GPA formatted to 2 decimal places - format_record(name, grade, gpa) — returns a formatted record string

Write a main.py that imports both modules and processes a list of student records, validating each before formatting.


Part D: Packages and Project Structure ⭐⭐⭐

D.1. Design (on paper or in a text file) the package structure for a library management system. It should have modules for: - Book data (title, author, ISBN, availability) - Member data (name, ID, borrowed books) - Transactions (borrow, return, overdue tracking) - Display (formatted output for receipts and reports)

Draw the directory tree. Write the __init__.py that exports the most commonly used functions.

D.2. You encounter this error:

ImportError: cannot import name 'process_data' from partially initialized
module 'analytics' (most likely due to a circular import)

Your files look like this:

# analytics.py
from reporting import generate_report

def process_data(data):
    return [d * 2 for d in data]
# reporting.py
from analytics import process_data

def generate_report(data):
    processed = process_data(data)
    print(f"Report: {processed}")

Explain why this error occurs. Then fix it using two different approaches.

D.3. Extend the TaskFlow v1.1 project checkpoint by adding a new module stats.py that provides: - count_by_priority(tasks) — returns a dict showing how many tasks are in each priority level - count_by_status(tasks) — returns counts of completed vs. incomplete tasks - completion_rate(tasks) — returns the percentage of tasks that are completed

Integrate stats.py into the main menu as a new option "6. View statistics" (shifting Quit to option 7).


Part M: Mixed Practice (Interleaved) ⭐⭐

These problems review concepts from earlier chapters in the context of modules.

M.1. Review: Functions (Ch 6). Explain how modules extend the concept of functions. Specifically, how does a module's namespace relate to a function's local scope?

M.2. Review: File I/O (Ch 10). Rewrite the following code to use pathlib instead of os.path:

import os

data_dir = os.path.join("project", "data")
input_file = os.path.join(data_dir, "input.csv")
output_file = os.path.join(data_dir, "output.csv")

if os.path.exists(input_file):
    print(f"Processing {os.path.basename(input_file)}...")

M.3. Review: Error Handling (Ch 11). Write a function safe_import(module_name) that attempts to import a module by name (using importlib.import_module()) and returns the module object if successful, or None if the module is not installed. Test it with "json" (should succeed) and "nonexistent_module_xyz" (should return None).

M.4. Review: Dictionaries (Ch 9). Use collections.Counter and collections.defaultdict to analyze this list of student course enrollments:

enrollments = [
    ("Alice", "CS101"), ("Bob", "CS101"), ("Alice", "MATH201"),
    ("Charlie", "CS101"), ("Bob", "MATH201"), ("Alice", "ENG102"),
    ("Charlie", "ENG102"), ("Diana", "CS101"), ("Diana", "MATH201"),
]

Produce: (a) the most popular course, (b) a dict mapping each student to their list of courses.


Part E: Research & Extension ⭐⭐⭐⭐

E.1. Explore the Python Package Index (pypi.org). Find three third-party packages that would be useful in a field you're interested in (data science, game development, web development, etc.). For each, report: (a) the package name, (b) what it does, (c) how many downloads it has, (d) a one-line code example from its documentation.

E.2. Research the concept of "dependency hell" in software development. Write a 200-word explanation of what it is, why it happens, and how Python's virtual environments (venv) help address it. Include a real-world example of a famous dependency incident (e.g., the left-pad incident in the JavaScript ecosystem).


Solutions

Selected solutions in appendices/answers-to-selected.md.