JavaScript – Chapter 2: Development Environment
Learn what JavaScript is, where it runs, why it is important, and how beginners can start using it with simple examples and clear output.
Main reading content
Chapter 32: Advanced Standard Library Tools
A beginner-friendly guide to advanced Python standard library modules for functional programming, iteration, inspection, type hints, command-line programs, logging, process management, and reusable software design.
Chapter 32 Topics
- 32.1
functools - 32.2
lru_cache - 32.3
cached_property - 32.4
partial - 32.5
singledispatch - 32.6
itertools - 32.7
operator - 32.8
inspect - 32.9
traceback - 32.10
warnings - 32.11
weakref - 32.12
gc - 32.13
types - 32.14
typing - 32.15
contextlib - 32.16
abc - 32.17
importlib - 32.18
subprocess - 32.19
argparse - 32.20
logging - 32.21 Practical Standard Library Projects
- 32.22 Chapter Practice Exercises
32.1 functools
```
The functools module provides tools that work with functions and callable objects. It includes utilities for caching results, creating partially configured functions, combining values, preserving decorator information, and choosing function behavior based on argument type.
These tools are especially useful in functional programming, reusable libraries, decorators, data processing, and performance improvement. Beginners should first understand ordinary functions before using the advanced helpers in this module.
Example: Using reduce()
from functools import reduce
```
numbers = [2, 3, 4, 5]
# Multiply all numbers together
product = reduce(
lambda current, number: current * number,
numbers
)
print("Numbers:", numbers)
print("Product:", product)
```
Output
Numbers: [2, 3, 4, 5]
```
Product: 120
```
Output Explanation
The reduce() function repeatedly combines two values. It first multiplies 2 by 3, then multiplies the result by 4, and finally multiplies that result by 5.
Example: Preserving Decorator Information
from functools import wraps
```
def announce(function):
@wraps(function)
def wrapper(*args, **kwargs):
print("Function is starting.")
result = function(*args, **kwargs)
print("Function has finished.")
return result
```
return wrapper
```
@announce
def greet(name):
"""Display a greeting."""
print("Hello,", name)
greet("Sara")
print("Function name:", greet.**name**)
print("Documentation:", greet.**doc**)
```
Output
Function is starting.
```
Hello, Sara
Function has finished.
Function name: greet
Documentation: Display a greeting.
```
The @wraps decorator preserves the original function's name and documentation after it is wrapped by another function.
32.2 lru_cache
```
The lru_cache decorator remembers previous function results. When the function receives the same arguments again, Python can return the stored result instead of repeating the calculation.
LRU means least recently used. When the cache reaches its maximum size, older unused results may be removed. Caching works best with functions that always return the same result for the same arguments.
Example
from functools import lru_cache
```
@lru_cache(maxsize=128)
def fibonacci(number):
# Base cases
if number < 2:
return number
```
# Recursive calculation
return (
fibonacci(number - 1)
+ fibonacci(number - 2)
)
```
print("Fibonacci result:", fibonacci(30))
print("Cache information:", fibonacci.cache_info())
```
Example Output
Fibonacci result: 832040
```
Cache information: CacheInfo(hits=28, misses=31, maxsize=128, currsize=31)
```
Output Explanation
Without caching, the recursive function would repeat many calculations. The cache stores results for previous numbers, making the calculation much faster. Cache statistics show successful reuse and newly calculated values.
Clearing the Cache
fibonacci.cache_clear()
```
print(fibonacci.cache_info())
```
Output
CacheInfo(hits=0, misses=0, maxsize=128, currsize=0)
```
32.3 cached_property
```
The cached_property decorator creates a property that is calculated once and then stored on the object. Later access returns the saved value instead of repeating the calculation.
It is useful for expensive calculations that depend on object data that does not change frequently. If the underlying data changes, the cached value may need to be deleted so it can be calculated again.
Example
from functools import cached_property
```
class Student:
def **init**(self, name, scores):
self.name = name
self.scores = scores
```
@cached_property
def average(self):
print("Calculating average...")
return sum(self.scores) / len(self.scores)
```
student = Student(
"Michael",
[80, 90, 85]
)
print("First access:", student.average)
print("Second access:", student.average)
```
Output
Calculating average...
```
First access: 85.0
Second access: 85.0
```
Output Explanation
The calculation message appears only once. On the second access, Python returns the cached result stored on the object.
Refresh the Cached Value
student.scores.append(100)
```
# Remove the stored cached value
del student.average
print("Updated average:", student.average)
```
Output
Calculating average...
```
Updated average: 88.75
32.4 partial
```
The partial function creates a new callable with some arguments already filled in. This allows a general-purpose function to be converted into a more specialized function.
It is useful when the same argument values are repeatedly passed to a function. The original function remains unchanged, while the partial function supplies the preselected values automatically.
Example
from functools import partial
```
def calculate_price(price, tax_rate, discount):
discounted_price = price * (1 - discount)
tax = discounted_price * tax_rate
```
return round(discounted_price + tax, 2)
```
# Create a function with Ontario tax already supplied
ontario_price = partial(
calculate_price,
tax_rate=0.13,
discount=0
)
# Create a discounted version
sale_price = partial(
calculate_price,
tax_rate=0.13,
discount=0.20
)
print("Regular total:", ontario_price(price=100))
print("Sale total:", sale_price(price=100))
```
Output
Regular total: 113.0
```
Sale total: 90.4
```
Output Explanation
Both new functions use the same original calculation. The first supplies only the tax rate, while the second also supplies a 20 percent discount.
```32.5 singledispatch
```
The singledispatch decorator allows one function name to have different implementations based on the type of its first argument. This is called single dispatch because only the first argument's type controls the selected implementation.
It is useful when several data types should be processed differently but share one clear function name. A default implementation handles unsupported types.
Example
from functools import singledispatch
```
@singledispatch
def describe(value):
print("Unsupported value:", value)
@describe.register
def _(value: int):
print("Integer:", value)
@describe.register
def _(value: str):
print("Text:", value)
@describe.register
def _(value: list):
print("List containing", len(value), "items")
describe(25)
describe("Python")
describe([1, 2, 3])
describe(4.5)
```
Output
Integer: 25
```
Text: Python
List containing 3 items
Unsupported value: 4.5
```
Output Explanation
Python selects the integer, string, or list implementation according to the first argument. The floating-point value uses the default implementation because no float version was registered.
```32.6 itertools
```
The itertools module provides efficient tools for working with iterators. It can combine collections, repeat values, generate combinations, group adjacent items, count continuously, and slice iterators.
Many itertools functions return lazy iterators. This means values are created only when requested, which can reduce memory use when processing large data.
Example: chain()
from itertools import chain
```
first_group = ["Ali", "Sara"]
second_group = ["Michael", "Emma"]
all_students = list(
chain(first_group, second_group)
)
print(all_students)
```
Output
['Ali', 'Sara', 'Michael', 'Emma']
Example: Combinations and Permutations
from itertools import combinations, permutations
```
letters = ["A", "B", "C"]
print("Combinations:")
for item in combinations(letters, 2):
print(item)
print()
print("Permutations:")
for item in permutations(letters, 2):
print(item)
```
Output
Combinations:
```
('A', 'B')
('A', 'C')
('B', 'C')
Permutations:
('A', 'B')
('A', 'C')
('B', 'A')
('B', 'C')
('C', 'A')
('C', 'B')
```
Example: groupby()
from itertools import groupby
```
students = [
("A", "Ali"),
("A", "Sara"),
("B", "Michael"),
("B", "Emma")
]
for grade, group in groupby(
students,
key=lambda item: item[0]
):
names = [
student[1]
for student in group
]
```
print(grade, names)
Output
A ['Ali', 'Sara']
```
B ['Michael', 'Emma']
32.7 operator
```
The operator module provides function versions of Python operators and convenient tools for retrieving items and attributes. Examples include add(), mul(), itemgetter(), and attrgetter().
These functions are useful with sorting, mapping, reducing, and other functions that expect another function as an argument.
Example: Arithmetic Operators
import operator
```
print("Addition:", operator.add(10, 5))
print("Multiplication:", operator.mul(10, 5))
print("Greater than:", operator.gt(10, 5))
```
Output
Addition: 15
```
Multiplication: 50
Greater than: True
```
Example: Sort Dictionaries with itemgetter()
from operator import itemgetter
```
students = [
{"name": "Sara", "score": 92},
{"name": "Michael", "score": 81},
{"name": "Ali", "score": 88}
]
sorted_students = sorted(
students,
key=itemgetter("score"),
reverse=True
)
for student in sorted_students:
print(student["name"], student["score"])
```
Output
Sara 92
```
Ali 88
Michael 81
```
Output Explanation
The itemgetter("score") callable retrieves each dictionary's score and gives it to sorted() as the sorting value.
32.8 inspect
```
The inspect module examines live Python objects. It can inspect functions, methods, classes, modules, parameters, documentation, and source information.
This process is called introspection because the program examines its own structure. It is useful for debugging, testing, documentation tools, decorators, and frameworks.
Example
import inspect
```
def calculate_total(
price: float,
quantity: int = 1
) -> float:
"""Calculate the complete purchase total."""
```
return price * quantity
```
signature = inspect.signature(calculate_total)
print("Function name:", calculate_total.**name**)
print("Signature:", signature)
print("Documentation:", inspect.getdoc(calculate_total))
print()
print("Parameters:")
for name, parameter in signature.parameters.items():
print(
name,
"- default:",
parameter.default,
"- annotation:",
parameter.annotation
)
```
Example Output
Function name: calculate_total
```
Signature: (price: float, quantity: int = 1) -> float
Documentation: Calculate the complete purchase total.
Parameters:
price - default: - annotation:
quantity - default: 1 - annotation:
```
Output Explanation
The module reads the function's name, parameter list, defaults, annotations, return type, and documentation. A missing default is represented internally by an empty marker.
```32.9 traceback
```
The traceback module provides tools for displaying and formatting exception information. A traceback shows which functions and lines were involved when an error occurred.
It is useful for logging detailed error reports, debugging production applications, and creating diagnostic information without immediately ending the complete program.
Example
import traceback
```
def divide_numbers(first, second):
return first / second
try:
result = divide_numbers(10, 0)
except ZeroDivisionError:
print("A division error occurred.")
```
formatted_error = traceback.format_exc()
print()
print("Detailed traceback:")
print(formatted_error)
Example Output
A division error occurred.
```
Detailed traceback:
Traceback (most recent call last):
File "example.py", line 8, in
result = divide_numbers(10, 0)
File "example.py", line 4, in divide_numbers
return first / second
ZeroDivisionError: division by zero
```
Output Explanation
The exception is caught so the program can display a friendly message. The formatted traceback still provides the detailed path that led to the error.
```32.10 warnings
```
The warnings module displays messages about conditions that are important but do not always require the program to stop. Warnings are commonly used for deprecated features, risky behavior, or values that may produce unexpected results.
A warning differs from an exception. An exception normally interrupts the current operation, while a warning usually allows the program to continue.
Example
import warnings
```
def old_calculate_total(price, tax):
warnings.warn(
"old_calculate_total() is deprecated. "
"Use calculate_total() instead.",
DeprecationWarning,
stacklevel=2
)
```
return price + tax
```
warnings.simplefilter(
"always",
DeprecationWarning
)
result = old_calculate_total(100, 13)
print("Result:", result)
```
Example Output
example.py:18: DeprecationWarning: old_calculate_total() is deprecated. Use calculate_total() instead.
```
result = old_calculate_total(100, 13)
Result: 113
```
Output Explanation
The warning tells programmers that the old function should be replaced. The function still returns its result, so the program continues.
```32.11 weakref
```
The weakref module creates weak references to objects. A normal reference keeps an object alive, while a weak reference allows the object to be removed when no normal references remain.
Weak references are useful in caches, registries, observer systems, and applications that should remember objects without preventing automatic memory cleanup.
Example
import weakref
```
class Student:
def **init**(self, name):
self.name = name
student = Student("Sara")
# Create a weak reference
student_reference = weakref.ref(student)
print("Before deletion:")
print(student_reference().name)
# Remove the normal reference
del student
print()
print("After deletion:")
print(student_reference())
```
Output
Before deletion:
```
Sara
After deletion:
None
```
Output Explanation
The weak reference can access the object while a normal reference exists. After the normal reference is deleted and the object is collected, calling the weak reference returns None.
32.12 gc
```
The gc module provides access to Python's cyclic garbage collector. Python automatically manages memory, but circular references can sometimes require special detection.
Most beginner programs do not need to control garbage collection directly. The module is mainly useful for debugging memory problems, inspecting tracked objects, and forcing a collection during testing.
Example
import gc
```
class Node:
def **init**(self, name):
self.name = name
self.other = None
first = Node("First")
second = Node("Second")
# Create a circular reference
first.other = second
second.other = first
# Remove the direct references
del first
del second
# Ask Python to collect unreachable objects
collected_objects = gc.collect()
print(
"Objects collected:",
collected_objects
)
print(
"Garbage collector enabled:",
gc.isenabled()
)
```
Example Output
Objects collected: 2
```
Garbage collector enabled: True
```
Output Explanation
The exact number may vary. The collector finds objects that can no longer be reached even though they referenced each other.
```32.13 types
```
The types module contains names for several built-in object types and tools for creating specialized objects. It includes function types, generator types, method types, namespaces, and read-only dictionary views.
It is useful when inspecting Python objects, creating simple namespace objects, attaching methods dynamically, or checking specific implementation-level types.
Example: SimpleNamespace
from types import SimpleNamespace
```
student = SimpleNamespace(
name="Michael",
score=85,
active=True
)
print("Name:", student.name)
print("Score:", student.score)
print("Active:", student.active)
student.score = 90
print("Updated score:", student.score)
```
Output
Name: Michael
```
Score: 85
Active: True
Updated score: 90
```
Example: Type Checking
import types
```
def greet():
return "Hello"
generator = (
number
for number in range(3)
)
print(
"Function:",
isinstance(greet, types.FunctionType)
)
print(
"Generator:",
isinstance(generator, types.GeneratorType)
)
```
Output
Function: True
```
Generator: True
32.14 typing
```
The typing module provides tools for type hints. Type hints describe the expected types of variables, parameters, return values, collections, and custom structures.
Python usually does not enforce type hints while the program runs. They mainly help programmers, editors, documentation tools, and static type checkers understand how code should be used.
Example
from typing import Optional
```
def find_student(
student_id: int,
students: dict[int, str]
) -> Optional[str]:
return students.get(student_id)
student_data = {
101: "Sara",
102: "Michael"
}
print(find_student(101, student_data))
print(find_student(999, student_data))
```
Output
Sara
```
None
```
Example: Type Alias and Typed Dictionary
from typing import TypedDict
```
class ProductRecord(TypedDict):
name: str
price: float
quantity: int
def calculate_total(
product: ProductRecord
) -> float:
return product["price"] * product["quantity"]
keyboard: ProductRecord = {
"name": "Keyboard",
"price": 49.99,
"quantity": 2
}
print(calculate_total(keyboard))
```
Output
99.98
Output Explanation
The typed dictionary describes the required keys and value types. The annotations make the expected data structure clearer without changing normal dictionary behavior.
```32.15 contextlib
```
The contextlib module provides utilities for creating and working with context managers. Context managers control setup and cleanup around a block used with the with statement.
Useful tools include contextmanager, suppress, redirect_stdout, closing, and ExitStack. They can reduce repeated resource-management code.
Example: Custom Context Manager
from contextlib import contextmanager
```
@contextmanager
def section(title):
print("=" * 40)
print(title)
print("=" * 40)
```
try:
yield
finally:
print("=" * 40)
print("Section finished")
```
with section("Student Report"):
print("Sara: 92")
print("Michael: 81")
```
Output
========================================
```
# Student Report
Sara: 92
Michael: 81
===========
Section finished
```
Example: Suppress an Expected Exception
from contextlib import suppress
```
from pathlib import Path
file_path = Path("temporary_file.txt")
file_path.write_text(
"Temporary information",
encoding="utf-8"
)
file_path.unlink()
# Ignore the error if the file is already gone
with suppress(FileNotFoundError):
file_path.unlink()
print("Program continued.")
```
Output
Program continued.
```
32.16 abc
```
The abc module supports abstract base classes. An abstract class defines a shared design for related subclasses and may require them to implement specific methods.
Abstract classes are useful when several classes must follow the same interface. They help detect incomplete subclasses before objects are created.
Example
from abc import ABC, abstractmethod
```
class PaymentMethod(ABC):
```
@abstractmethod
def pay(self, amount):
"""Process a payment."""
pass
```
class CreditCardPayment(PaymentMethod):
```
def pay(self, amount):
print(
"Credit card payment:",
f"${amount:.2f}"
)
```
class CashPayment(PaymentMethod):
```
def pay(self, amount):
print(
"Cash payment:",
f"${amount:.2f}"
)
```
payments = [
CreditCardPayment(),
CashPayment()
]
for payment in payments:
payment.pay(50)
```
Output
Credit card payment: $50.00
```
Cash payment: $50.00
```
Output Explanation
Both subclasses are required to implement the pay() method. The loop can use either payment object through the same shared interface.
32.17 importlib
```
The importlib module provides programmatic access to Python's import system. It can import modules using names stored in strings, reload modules, and inspect whether modules are available.
Dynamic imports are useful in plugin systems, configurable applications, command tools, and programs where the required module is not known until runtime.
Example
import importlib
```
module_name = "math"
# Import the module using a string
module = importlib.import_module(module_name)
print("Module:", module.**name**)
print("Square root:", module.sqrt(81))
print("Pi:", module.pi)
```
Output
Module: math
```
Square root: 9.0
Pi: 3.141592653589793
```
Check Whether a Module Exists
import importlib.util
```
module_name = "json"
module_information = importlib.util.find_spec(
module_name
)
if module_information is not None:
print(module_name, "is available.")
else:
print(module_name, "is not available.")
```
Output
json is available.
```
32.18 subprocess
```
The subprocess module runs external commands and programs from Python. It can capture their output, provide input, inspect return codes, and report command failures.
Commands should usually be passed as a list instead of one shell string. Avoid using untrusted user input in system commands. The shell=True option is unnecessary for most tasks and can create security risks.
Safe Cross-Platform Example
import subprocess
```
import sys
result = subprocess.run(
[
sys.executable,
"-c",
"print('Hello from another Python process')"
],
capture_output=True,
text=True,
check=True
)
print("Return code:", result.returncode)
print("Captured output:")
print(result.stdout.strip())
```
Output
Return code: 0
```
Captured output:
Hello from another Python process
```
Handling Command Errors
import subprocess
```
import sys
try:
subprocess.run(
[
sys.executable,
"-c",
"raise ValueError('Example failure')"
],
capture_output=True,
text=True,
check=True
)
except subprocess.CalledProcessError as error:
print("Command failed.")
print("Return code:", error.returncode)
```
Example Output
Command failed.
```
Return code: 1
32.19 argparse
```
The argparse module creates command-line interfaces. It defines positional arguments, optional flags, default values, help messages, required options, and value types.
It is more convenient than manually reading sys.argv because it validates input and automatically creates a help screen.
Example Program
import argparse
```
parser = argparse.ArgumentParser(
description="Calculate a product total."
)
parser.add_argument(
"price",
type=float,
help="Price of one item"
)
parser.add_argument(
"quantity",
type=int,
help="Number of items"
)
parser.add_argument(
"--tax",
type=float,
default=0.13,
help="Tax rate, such as 0.13"
)
arguments = parser.parse_args()
subtotal = arguments.price * arguments.quantity
tax = subtotal * arguments.tax
total = subtotal + tax
print("Subtotal:", round(subtotal, 2))
print("Tax:", round(tax, 2))
print("Total:", round(total, 2))
```
Run Command
python calculator.py 25.50 3 --tax 0.13
Output
Subtotal: 76.5
```
Tax: 9.95
Total: 86.45
```
Help Command
python calculator.py --help
Example Help Output
usage: calculator.py [-h] [--tax TAX] price quantity
```
Calculate a product total.
positional arguments:
price Price of one item
quantity Number of items
options:
-h, --help show this help message and exit
--tax TAX Tax rate, such as 0.13
32.20 logging
```
The logging module records messages about program activity. It is more flexible than print() because messages can have severity levels, timestamps, module names, and output destinations.
Common levels are DEBUG, INFO, WARNING, ERROR, and CRITICAL. Logging is useful for debugging, monitoring, error reports, and production applications.
Example
import logging
```
logging.basicConfig(
level=logging.INFO,
format=(
"%(asctime)s | "
"%(levelname)s | "
"%(message)s"
)
)
logging.debug("Detailed debugging information")
logging.info("Program started")
logging.warning("The storage space is low")
logging.error("The requested file was not found")
logging.critical("The application cannot continue")
```
Example Output
2026-07-19 12:00:00,000 | INFO | Program started
```
2026-07-19 12:00:00,001 | WARNING | The storage space is low
2026-07-19 12:00:00,001 | ERROR | The requested file was not found
2026-07-19 12:00:00,001 | CRITICAL | The application cannot continue
```
Output Explanation
The debugging message is hidden because the configured minimum level is INFO. The other messages appear with timestamps and severity levels.
Log to a File
import logging
```
logging.basicConfig(
filename="application.log",
level=logging.INFO,
format="%(asctime)s | %(levelname)s | %(message)s"
)
logging.info("Application started")
logging.info("User opened the report")
logging.warning("A record was incomplete")
print("Log messages were written to application.log")
```
Output
Log messages were written to application.log
```
32.21 Practical Standard Library Projects
```
Advanced standard library modules are most useful when several tools work together. A command-line report processor can combine argparse, logging, functools, typing, pathlib, and statistics.
The following project reads student scores from a text file, validates the records, calculates summaries, supports command-line options, caches file loading, and writes activity messages to a log file.
Project: Command-Line Student Report Processor
from __future__ import annotations
```
import argparse
import logging
import statistics
from dataclasses import dataclass
from functools import lru_cache
from pathlib import Path
from typing import Iterable
@dataclass(frozen=True)
class Student:
name: str
score: float
```
@property
def grade(self) -> str:
if self.score >= 90:
return "A"
if self.score >= 80:
return "B"
if self.score >= 70:
return "C"
if self.score >= 60:
return "D"
return "F"
```
def configure_logging(log_file: Path) -> None:
logging.basicConfig(
filename=log_file,
level=logging.INFO,
format=(
"%(asctime)s | "
"%(levelname)s | "
"%(message)s"
)
)
def create_sample_file(file_path: Path) -> None:
if file_path.exists():
return
```
sample_data = (
"Sara,92\n"
"Michael,81\n"
"Ali,88\n"
"Emma,68\n"
"David,75\n"
"Invalid Record\n"
"Mary,not-a-number\n"
)
file_path.write_text(
sample_data,
encoding="utf-8"
)
logging.info(
"Created sample file: %s",
file_path
)
```
@lru_cache(maxsize=16)
def load_students(
file_name: str
) -> tuple[Student, ...]:
file_path = Path(file_name)
```
students: list[Student] = []
for line_number, line in enumerate(
file_path.read_text(
encoding="utf-8"
).splitlines(),
start=1
):
cleaned_line = line.strip()
if not cleaned_line:
continue
parts = [
part.strip()
for part in cleaned_line.split(",")
]
if len(parts) != 2:
logging.warning(
"Invalid record on line %s: %s",
line_number,
cleaned_line
)
continue
name, score_text = parts
try:
score = float(score_text)
except ValueError:
logging.warning(
"Invalid score on line %s: %s",
line_number,
score_text
)
continue
if not 0 <= score <= 100:
logging.warning(
"Score outside valid range on line %s",
line_number
)
continue
students.append(
Student(
name=name.title(),
score=score
)
)
logging.info(
"Loaded %s valid students",
len(students)
)
return tuple(students)
```
def filter_students(
students: Iterable[Student],
minimum_score: float
) -> list[Student]:
return [
student
for student in students
if student.score >= minimum_score
]
def display_report(
students: list[Student]
) -> None:
if not students:
print("No students matched the selected rules.")
return
```
scores = [
student.score
for student in students
]
print("STUDENT REPORT")
print("=" * 50)
for student in sorted(
students,
key=lambda item: item.score,
reverse=True
):
print(
f"{student.name:<20} "
f"{student.score:>6.2f} "
f"Grade {student.grade}"
)
print("=" * 50)
print(
"Students:",
len(students)
)
print(
"Average:",
round(statistics.mean(scores), 2)
)
print(
"Median:",
round(statistics.median(scores), 2)
)
print(
"Highest:",
max(scores)
)
print(
"Lowest:",
min(scores)
)
```
def create_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description=(
"Read student scores and "
"create a class report."
)
)
```
parser.add_argument(
"--file",
default="student_scores.txt",
help="Path to the student score file"
)
parser.add_argument(
"--minimum",
type=float,
default=0,
help="Display students at or above this score"
)
parser.add_argument(
"--log",
default="student_report.log",
help="Path to the log file"
)
return parser
```
def main() -> None:
parser = create_parser()
arguments = parser.parse_args()
```
score_file = Path(arguments.file)
log_file = Path(arguments.log)
configure_logging(log_file)
logging.info("Program started")
create_sample_file(score_file)
students = load_students(
str(score_file.resolve())
)
selected_students = filter_students(
students,
arguments.minimum
)
display_report(selected_students)
logging.info(
"Displayed %s student records",
len(selected_students)
)
logging.info("Program finished")
```
if **name** == "**main**":
main()
```
Run the Complete Report
python student_report.py
Output
STUDENT REPORT
```
==================================================
Sara 92.00 Grade A
Ali 88.00 Grade B
Michael 81.00 Grade B
David 75.00 Grade C
Emma 68.00 Grade D
==================================
Students: 5
Average: 80.8
Median: 81.0
Highest: 92.0
Lowest: 68.0
```
Run with a Minimum Score
python student_report.py --minimum 80
Output
STUDENT REPORT
```
==================================================
Sara 92.00 Grade A
Ali 88.00 Grade B
Michael 81.00 Grade B
==================================
Students: 3
Average: 87.0
Median: 88.0
Highest: 92.0
Lowest: 81.0
```
Project Explanation
The Student data class stores the student name and score. It is frozen so records cannot be accidentally changed after creation. The grade property converts the numeric score into a letter grade.
The argparse module defines options for the score file, minimum score, and log file. Users can change these settings without editing the program.
The logging module records program activity and invalid records. This keeps technical details in a log file while the terminal displays the main report.
The lru_cache decorator stores previously loaded results. When the same absolute filename is requested again, Python can return the cached student tuple.
Type hints describe expected parameter and return types. The Iterable annotation allows the filtering function to accept several iterable collection types.
Invalid lines and nonnumeric scores are skipped instead of stopping the complete program. Valid records are sorted from highest to lowest before being displayed.
How to Run the Project
- Open Visual Studio Code, IDLE, PyCharm, or another Python editor.
- Create a file named
student_report.py. - Copy the complete project into the file.
- Save the file.
- Open a terminal in the same folder.
- Run
python student_report.py. - On some computers, run
python3 student_report.py. - Review the automatically created
student_scores.txtfile. - Review the generated
student_report.logfile. - Run the program with
--minimum 80. - Run
python student_report.py --helpto view all options.
Project Challenges
- Add a command-line option for maximum score.
- Add an option to filter by letter grade.
- Write the report to a text file.
- Export valid records to CSV.
- Add student identification numbers.
- Group students by grade using
itertools.groupby(). - Use
Counterto create a grade distribution. - Add a warning when the class average is below 70.
- Create a cached property for grade calculations.
- Create a subclass-based report system with
abc. - Run another Python reporting script with
subprocess. - Load optional report plugins with
importlib.
32.22 Chapter Practice Exercises
```These exercises help you practise the advanced standard library modules introduced in this chapter. Start with individual module exercises and then combine several modules in larger projects.
- Use
reduce()to calculate the product of a number list. - Create a decorator that preserves function information with
wraps. - Cache a recursive Fibonacci function with
lru_cache. - Display and clear cache statistics.
- Create a class with an expensive
cached_property. - Delete and recalculate a cached property.
- Create a partially configured tax function.
- Create partial functions for several discount rates.
- Use
singledispatchto process strings, integers, and lists. - Combine several iterables with
itertools.chain(). - Create combinations of student pairs.
- Create permutations of three letters.
- Group sorted records with
groupby(). - Sort dictionaries using
itemgetter(). - Sort objects using
attrgetter(). - Inspect a function's parameters and return annotation.
- Display a formatted traceback after catching an exception.
- Create a custom deprecation warning.
- Create and test a weak reference.
- Create a circular reference and call
gc.collect(). - Create a record using
SimpleNamespace. - Check whether an object is a generator.
- Add type hints to a student-processing function.
- Create a
TypedDictfor an order. - Create a context manager with
contextmanager. - Suppress an expected file error safely.
- Create an abstract class for report exporters.
- Implement text and CSV exporter subclasses.
- Import the
mathmodule dynamically. - Check whether a named module is available.
- Run a child Python process with
subprocess. - Capture the child process output.
- Create an
argparsecalculator. - Add optional command-line arguments and defaults.
- Create a command-line help screen.
- Configure logging with timestamps and levels.
- Write log records to a file.
- Build a cached command-line report processor.
- Build a plugin loader with
importlib. - Build an abstract payment processing system.
Practice Example: Priority Task Processor
import argparse
```
import logging
from dataclasses import dataclass
from operator import attrgetter
@dataclass
class Task:
name: str
priority: int
completed: bool = False
def create_parser():
parser = argparse.ArgumentParser(
description="Display tasks by priority."
)
```
parser.add_argument(
"--minimum-priority",
type=int,
default=1,
help="Lowest priority to display"
)
return parser
```
logging.basicConfig(
level=logging.INFO,
format="%(levelname)s | %(message)s"
)
tasks = [
Task("Send report", 5),
Task("Clean desk", 1),
Task("Call customer", 4),
Task("Update website", 3),
Task("Old completed task", 5, True)
]
parser = create_parser()
arguments = parser.parse_args()
selected_tasks = [
task
for task in tasks
if (
not task.completed
and task.priority >= arguments.minimum_priority
)
]
selected_tasks.sort(
key=attrgetter("priority"),
reverse=True
)
logging.info(
"Selected %s tasks",
len(selected_tasks)
)
print("TASKS")
print("-" * 40)
for task in selected_tasks:
print(
task.name,
"- Priority:",
task.priority
)
```
Run Command
python task_processor.py --minimum-priority 3
Output
INFO | Selected 3 tasks
```
## TASKS
Send report - Priority: 5
Call customer - Priority: 4
Update website - Priority: 3
```
Output Explanation
The command-line option controls the minimum priority. Completed tasks are removed, remaining tasks are sorted by priority, and logging reports how many tasks were selected.
```A modern course built to help learners study step by step with clarity, comfort, and confidence.