EASYTUTORGUIDE

Practical tutorials, tools, courses, digital skills, and business promotion.

Free Learning

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.

Beginner Friendly JavaScript Basics Web Development Code Examples
JavaScript Lesson 2 Chapter 2 Topics Day / Night Mode

Main reading content

python-course-chapter-32

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.

Goal: Learn how advanced standard library tools can make Python programs faster, safer, easier to inspect, more reusable, and easier to maintain.

Chapter 32 Topics

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

  1. Open Visual Studio Code, IDLE, PyCharm, or another Python editor.
  2. Create a file named student_report.py.
  3. Copy the complete project into the file.
  4. Save the file.
  5. Open a terminal in the same folder.
  6. Run python student_report.py.
  7. On some computers, run python3 student_report.py.
  8. Review the automatically created student_scores.txt file.
  9. Review the generated student_report.log file.
  10. Run the program with --minimum 80.
  11. Run python student_report.py --help to 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 Counter to 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.

  1. Use reduce() to calculate the product of a number list.
  2. Create a decorator that preserves function information with wraps.
  3. Cache a recursive Fibonacci function with lru_cache.
  4. Display and clear cache statistics.
  5. Create a class with an expensive cached_property.
  6. Delete and recalculate a cached property.
  7. Create a partially configured tax function.
  8. Create partial functions for several discount rates.
  9. Use singledispatch to process strings, integers, and lists.
  10. Combine several iterables with itertools.chain().
  11. Create combinations of student pairs.
  12. Create permutations of three letters.
  13. Group sorted records with groupby().
  14. Sort dictionaries using itemgetter().
  15. Sort objects using attrgetter().
  16. Inspect a function's parameters and return annotation.
  17. Display a formatted traceback after catching an exception.
  18. Create a custom deprecation warning.
  19. Create and test a weak reference.
  20. Create a circular reference and call gc.collect().
  21. Create a record using SimpleNamespace.
  22. Check whether an object is a generator.
  23. Add type hints to a student-processing function.
  24. Create a TypedDict for an order.
  25. Create a context manager with contextmanager.
  26. Suppress an expected file error safely.
  27. Create an abstract class for report exporters.
  28. Implement text and CSV exporter subclasses.
  29. Import the math module dynamically.
  30. Check whether a named module is available.
  31. Run a child Python process with subprocess.
  32. Capture the child process output.
  33. Create an argparse calculator.
  34. Add optional command-line arguments and defaults.
  35. Create a command-line help screen.
  36. Configure logging with timestamps and levels.
  37. Write log records to a file.
  38. Build a cached command-line report processor.
  39. Build a plugin loader with importlib.
  40. 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.

```
Chapter 2: Development EnvironmentPrepare Your JavaScript Workspace

A modern course built to help learners study step by step with clarity, comfort, and confidence.