Lab 3
Lab 3 — Modules, Packages, Files, Exceptions and Type Hints
Objectives
This laboratory moves from individual Python scripts and classes to the structure of complete Python applications.
Students already know how to split C/C++ applications into multiple source and header files. The objective of this laboratory is to understand the equivalent mechanisms in Python and to learn the conventions used for organizing real Python projects.
After completing this laboratory, you should be able to:
- split a Python program into multiple modules;
- create and use Python packages;
- understand the role of
__init__.py; - use absolute and relative imports;
- understand the purpose of
if __name__ == "__main__"; - read and write text files;
- use
pathlibfor filesystem paths; - read and write CSV and JSON files;
- use exceptions for error handling;
- define custom exceptions;
- use
try,except,elseandfinally; - use context managers and the
withstatement; - write type hints for variables, functions and collections;
- use
Optional, unions and type aliases; - understand the purpose of static type checkers;
- organize a small Python project using a
src/andtests/layout.
Throughout this laboratory, Python concepts are compared with familiar C/C++ mechanisms whenever a direct comparison is useful.
1. From translation units to modules
In C/C++, larger programs are usually split into header and source files.
In Python, a source file is also a module.
| C/C++ | Python |
|---|---|
project/
├── main.cpp
├── math_utils.cpp
└── math_utils.hpp
|
project/
├── main.py
└── math_utils.py
|
A Python file can contain:
- functions;
- classes;
- constants;
- executable statements.
For example:
# math_utils.py
def add(a, b):
return a + b
def multiply(a, b):
return a * b
2. Importing a module
The Python import statement plays a role similar to including declarations and linking implementation code in C/C++.
| C/C++ | Python |
|---|---|
#include "math_utils.hpp"
int result = add(2, 3);
|
import math_utils
result = math_utils.add(2, 3)
|
Using the module name explicitly makes the origin of the function clear.
3. Importing specific names
Python can import individual names from a module.
| C/C++ | Python |
|---|---|
#include "math_utils.hpp"
int result = add(2, 3);
|
from math_utils import add
result = add(2, 3)
|
Both styles are common:
import math_utils
math_utils.add(2, 3)
or:
from math_utils import add
add(2, 3)
4. Import aliases
Modules or imported names can be given aliases.
| C/C++ | Python |
|---|---|
namespace fs = std::filesystem;
|
import numpy as np
import pandas as pd
|
This style is common when a library has a well-known conventional alias.
Aliases should improve readability rather than obscure the origin of a name.
5. Avoid wildcard imports
Python allows:
from math_utils import *
However, this is generally discouraged.
It makes it difficult to determine where a name originated.
Compare:
| Less explicit | Preferred |
|---|---|
from math_utils import *
result = add(2, 3)
|
from math_utils import add
result = add(2, 3)
|
or:
import math_utils
result = math_utils.add(2, 3)
6. Module execution
A Python module may be:
- executed directly;
- imported by another module.
This distinction is visible through the special variable __name__.
| C/C++ | Python |
|---|---|
int main()
{
run_program();
return 0;
}
|
def main():
run_program()
if __name__ == "__main__":
main()
|
When a file is executed directly:
__name__ == "__main__"
When it is imported, __name__ contains the module name.
7. Why the main guard matters
Consider the following file:
# tools.py
print("Initializing tools")
def calculate():
return 42
Importing it:
import tools
will immediately print:
Initializing tools
Top-level statements execute when a module is imported.
For code that should run only when the file is executed directly:
def main():
print("Running application")
if __name__ == "__main__":
main()
8. Packages
A package groups related modules into a directory.
A simple package might look like:
project/
├── main.py
└── calculator/
├── __init__.py
├── arithmetic.py
└── statistics.py
The equivalent organizational idea in C++ might involve namespaces and multiple translation units.
| C++ | Python |
|---|---|
calculator/
├── arithmetic.cpp
├── arithmetic.hpp
├── statistics.cpp
└── statistics.hpp
|
calculator/
├── __init__.py
├── arithmetic.py
└── statistics.py
|
9. __init__.py
Traditionally, __init__.py marks a directory as a Python package.
It can be empty:
# calculator/__init__.py
It can also define which names are conveniently exposed by the package.
For example:
# calculator/__init__.py
from .arithmetic import add
from .statistics import average
Then users can write:
from calculator import add, average
instead of:
from calculator.arithmetic import add
from calculator.statistics import average
10. Absolute imports
An absolute import starts from the top-level package.
Suppose the project is:
project/
└── app/
├── __init__.py
├── models.py
└── services.py
Inside services.py:
from app.models import Student
This is an absolute import.
11. Relative imports
Modules inside a package may also use relative imports.
Inside services.py:
from .models import Student
A single dot means:
the current package
Two dots mean the parent package:
from ..utils import load_config
For student projects, absolute imports are often easier to read unless package-local relative imports clearly improve organization.
12. Namespaces
C++ namespaces and Python modules/packages solve related organizational problems.
| C++ | Python |
|---|---|
namespace math_utils {
int add(int a, int b)
{
return a + b;
}
}
int result =
math_utils::add(2, 3);
|
# math_utils.py
def add(a, b):
return a + b
import math_utils
result = math_utils.add(2, 3)
|
A module naturally provides a namespace.
13. A first multi-file Python program
Consider:
student_app/
├── main.py
├── models.py
└── services.py
models.py:
from dataclasses import dataclass
@dataclass
class Student:
name: str
grade: float
services.py:
from models import Student
def average(students: list[Student]) -> float:
total = sum(student.grade for student in students)
return total / len(students)
main.py:
from models import Student
from services import average
def main():
students = [
Student("Alice", 9.5),
Student("Bob", 8.0),
]
print(average(students))
if __name__ == "__main__":
main()
14. Reading text files
C++ commonly uses std::ifstream.
Python uses the built-in open() function.
| C++ | Python |
|---|---|
#include <fstream>
#include <string>
std::ifstream file("data.txt");
std::string line;
while (std::getline(file, line)) {
std::cout << line << "\n";
}
|
with open("data.txt", "r") as file:
for line in file:
print(line, end="")
|
The Python with statement automatically closes the file.
15. Context managers
The with statement is used with objects that manage resources.
It is conceptually related to deterministic resource management in C++.
| C++ RAII | Python context manager |
|---|---|
{
std::ifstream file("data.txt");
// Use file.
} // file is closed here
|
with open("data.txt") as file:
# Use file.
...
# File is closed here.
|
The context manager guarantees cleanup even if an exception occurs inside the block.
16. Reading an entire file
| C++ | Python |
|---|---|
std::ifstream file("data.txt");
std::string content(
(std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>()
);
|
with open("data.txt", "r") as file:
content = file.read()
|
17. Reading lines
Python can read all lines into a list:
with open("data.txt", "r") as file:
lines = file.readlines()
Usually, if the entire list is not required, direct iteration is preferable:
with open("data.txt", "r") as file:
for line in file:
...
This avoids loading the entire file into memory.
18. Writing text files
| C++ | Python |
|---|---|
std::ofstream file("output.txt");
file << "Hello\n";
file << "Value: " << value << "\n";
|
with open("output.txt", "w") as file:
file.write("Hello\n")
file.write(f"Value: {value}\n")
|
Common modes include:
| Mode | Meaning |
|---|---|
"r"
|
read |
"w"
|
write, replacing existing content |
"a"
|
append |
"x"
|
create, failing if the file already exists |
"rb"
|
binary read |
"wb"
|
binary write |
19. Text encoding
For text files, specifying an encoding explicitly is recommended.
with open(
"data.txt",
"r",
encoding="utf-8",
) as file:
content = file.read()
This makes the program's assumptions explicit and avoids relying on platform-specific defaults.
20. pathlib
Modern Python code commonly uses pathlib.Path instead of manually manipulating path strings.
| C++ | Python |
|---|---|
#include <filesystem>
namespace fs = std::filesystem;
fs::path path =
fs::path("data") / "students.txt";
|
from pathlib import Path
path = Path("data") / "students.txt"
|
The / operator joins path components.
21. Common pathlib operations
| Operation | Python |
|---|---|
| test whether path exists | path.exists()
|
| test whether path is a file | path.is_file()
|
| test whether path is a directory | path.is_dir()
|
| get filename | path.name
|
| get extension | path.suffix
|
| get parent | path.parent
|
| create directory | path.mkdir()
|
Example:
from pathlib import Path
data_dir = Path("data")
data_dir.mkdir(
parents=True,
exist_ok=True,
)
22. Reading and writing with pathlib
For small text files, Path provides convenience methods.
| Traditional open() | pathlib |
|---|---|
with open(
"data.txt",
"r",
encoding="utf-8",
) as file:
content = file.read()
|
from pathlib import Path
path = Path("data.txt")
content = path.read_text(
encoding="utf-8"
)
|
Writing:
path.write_text(
"Hello\n",
encoding="utf-8",
)
23. CSV files
CSV data should generally be processed using Python's csv module rather than manually splitting lines.
Suppose:
name,grade
Alice,9.5
Bob,8.0
Carol,7.5
Reading it:
import csv
with open(
"students.csv",
"r",
encoding="utf-8",
newline="",
) as file:
reader = csv.reader(file)
for row in reader:
print(row)
24. CSV dictionaries
When the first row contains column names, csv.DictReader is often more readable.
import csv
with open(
"students.csv",
"r",
encoding="utf-8",
newline="",
) as file:
reader = csv.DictReader(file)
for row in reader:
print(row["name"], row["grade"])
Each row behaves like a dictionary.
25. Writing CSV files
import csv
students = [
["Alice", 9.5],
["Bob", 8.0],
]
with open(
"students.csv",
"w",
encoding="utf-8",
newline="",
) as file:
writer = csv.writer(file)
writer.writerow(["name", "grade"])
writer.writerows(students)
26. JSON files
JSON is commonly used for configuration files, APIs and structured data.
Example JSON:
{
"name": "Alice",
"year": 2,
"active": true
}
Python's standard library provides the json module.
27. Reading JSON
| Conceptual C++ approach | Python |
|---|---|
// Usually requires an external JSON library,
// for example nlohmann/json.
//
// std::ifstream file("config.json");
// json config = json::parse(file);
|
import json
with open(
"config.json",
"r",
encoding="utf-8",
) as file:
config = json.load(file)
|
The resulting Python object normally contains dictionaries, lists, strings, numbers, booleans and None.
28. Writing JSON
import json
config = {
"host": "localhost",
"port": 8000,
"debug": True,
}
with open(
"config.json",
"w",
encoding="utf-8",
) as file:
json.dump(
config,
file,
indent=4,
)
29. Exceptions
Python exceptions serve the same general purpose as C++ exceptions.
| C++ | Python |
|---|---|
try {
int value = std::stoi(text);
}
catch (const std::invalid_argument& e) {
std::cout << "Invalid input\n";
}
|
try:
value = int(text)
except ValueError:
print("Invalid input")
|
30. Raising exceptions
C++ uses throw.
Python uses raise.
| C++ | Python |
|---|---|
if (value < 0) {
throw std::invalid_argument(
"value cannot be negative"
);
}
|
if value < 0:
raise ValueError(
"value cannot be negative"
)
|
31. Catching multiple exception types
Python can handle different exception types separately.
| C++ | Python |
|---|---|
try {
// ...
}
catch (const std::invalid_argument& e) {
// ...
}
catch (const std::runtime_error& e) {
// ...
}
|
try:
...
except ValueError:
...
except RuntimeError:
...
|
32. Accessing the exception object
| C++ | Python |
|---|---|
catch (const std::exception& e) {
std::cerr << e.what() << "\n";
}
|
except ValueError as error:
print(error)
|
33. Avoid catching everything
This is possible:
try:
...
except Exception:
...
but catching broad exceptions without a clear reason can hide programming errors.
Prefer catching the specific errors that the code is expected to handle.
For example:
try:
value = int(text)
except ValueError:
print("Expected an integer")
34. try / except / else
Python provides an else block that executes only if no exception was raised.
try:
value = int(text)
except ValueError:
print("Invalid integer")
else:
print(f"Parsed value: {value}")
This can keep the protected section of code small.
35. finally
A finally block executes whether or not an exception occurred.
| C++ idea | Python |
|---|---|
try {
// Work.
}
catch (...) {
// Handle error.
}
// Cleanup must still happen,
// preferably through RAII.
|
try:
...
except ValueError:
...
finally:
print("Always executed")
|
For resource cleanup, context managers are usually preferable to manually written finally blocks.
36. Custom exceptions
Application-specific errors can be represented by custom exception classes.
| C++ | Python |
|---|---|
class InvalidGrade
: public std::runtime_error
{
public:
InvalidGrade()
: std::runtime_error(
"invalid grade"
)
{
}
};
|
class InvalidGradeError(ValueError):
pass
|
Usage:
if not 1 <= grade <= 10:
raise InvalidGradeError(
"grade must be between 1 and 10"
)
37. Exception hierarchy
Python exceptions form a class hierarchy.
A simplified part of it is:
BaseException
└── Exception
├── ValueError
├── TypeError
├── OSError
│ └── FileNotFoundError
├── KeyError
├── IndexError
└── RuntimeError
Catching a base class also catches derived exception types.
Therefore:
except OSError:
...
also catches errors such as FileNotFoundError.
38. Common exception types
| Exception | Typical meaning |
|---|---|
ValueError
|
correct type, invalid value |
TypeError
|
operation used with inappropriate type |
FileNotFoundError
|
requested file does not exist |
PermissionError
|
insufficient filesystem permissions |
KeyError
|
dictionary key does not exist |
IndexError
|
sequence index outside valid range |
ZeroDivisionError
|
division by zero |
39. EAFP versus LBYL
Python often follows the principle:
Easier to Ask Forgiveness than Permission (EAFP).
Instead of checking everything before an operation, perform the operation and handle the expected exception.
Compare:
| Check first | EAFP style |
|---|---|
if "grade" in student:
grade = student["grade"]
else:
grade = None
|
try:
grade = student["grade"]
except KeyError:
grade = None
|
This does not mean exceptions should be used for every branch. Use the style that expresses the intent clearly.
40. Type hints
Python is dynamically typed, but it supports optional static type annotations.
| C++ | Python |
|---|---|
int add(int a, int b)
{
return a + b;
}
|
def add(a: int, b: int) -> int:
return a + b
|
Python does not normally enforce these annotations at runtime.
They are primarily used by:
- IDEs;
- linters;
- static type checkers;
- documentation tools;
- programmers reading the code.
41. Variable annotations
Variables can also be annotated.
| C++ | Python |
|---|---|
int count = 0;
double average = 0.0;
std::string name = "Alice";
|
count: int = 0
average: float = 0.0
name: str = "Alice"
|
Annotations are optional when the type is obvious.
42. Collection type hints
Modern Python allows built-in collection types to be parameterized.
| C++ | Python |
|---|---|
std::vector<int> values;
std::unordered_map<
std::string,
double
> grades;
|
values: list[int] = []
grades: dict[str, float] = {}
|
Other examples:
names: set[str] = set()
position: tuple[float, float] = (
10.0,
20.0,
)
43. Function annotations with collections
| C++ | Python |
|---|---|
double average(
const std::vector<double>& values
);
|
def average(
values: list[float],
) -> float:
return sum(values) / len(values)
|
44. Optional values
Sometimes a function may return either a value or no value.
In C++, std::optional can express this.
Modern Python can use the union operator |.
| C++ | Python |
|---|---|
std::optional<Student> findStudent(
const std::string& name
);
|
def find_student(
name: str,
) -> Student | None:
...
|
Older Python code may use:
from typing import Optional
def find_student(
name: str,
) -> Optional[Student]:
...
45. Union types
A value may have more than one accepted type.
| C++ idea | Python |
|---|---|
std::variant<int, std::string> value;
|
value: int | str
|
Function example:
def parse_identifier(
value: int | str,
) -> str:
return str(value)
46. Type aliases
Complex type annotations can be given descriptive names.
| C++ | Python |
|---|---|
using StudentGrades =
std::unordered_map<
std::string,
std::vector<double>
>;
|
StudentGrades = dict[
str,
list[float],
]
|
Then:
def average_grades(
grades: StudentGrades,
) -> dict[str, float]:
...
47. The Any type
Any means that static type checking should accept any type.
from typing import Any
value: Any
Use it carefully.
Overusing Any removes much of the benefit of static type checking.
48. Static type checking
A static type checker can analyze annotations without executing the program.
For example, with:
def add(a: int, b: int) -> int:
return a + b
result = add("hello", 3)
Python itself may only fail when that line executes.
A static checker can report the mismatch before execution.
A common checker is:
mypy .
Another modern option is:
pyright
49. Type hints are not runtime validation
This function:
def square(value: int) -> int:
return value * value
can still be called at runtime with another compatible object:
print(square(3.5))
Type hints do not automatically reject the call.
If runtime validation is required, it must be implemented separately.
50. Project structure
For a larger project, avoid placing every file in one directory.
A useful structure is:
student-project/
├── pyproject.toml
├── README.md
├── .gitignore
├── src/
│ └── student_project/
│ ├── __init__.py
│ ├── models.py
│ ├── services.py
│ ├── storage.py
│ └── main.py
└── tests/
├── test_services.py
└── test_storage.py
The src/ layout clearly separates application code from project metadata and tests.
51. C++ project layout versus Python project layout
| Typical C/C++ | Typical Python |
|---|---|
project/
├── CMakeLists.txt
├── include/
│ └── project/
│ └── model.hpp
├── src/
│ ├── main.cpp
│ └── model.cpp
└── tests/
└── test_model.cpp
|
project/
├── pyproject.toml
├── src/
│ └── project/
│ ├── __init__.py
│ ├── main.py
│ └── model.py
└── tests/
└── test_model.py
|
52. pyproject.toml
Modern Python projects commonly use pyproject.toml for project metadata and tool configuration.
A minimal example:
[project]
name = "student-project"
version = "0.1.0"
requires-python = ">=3.11"
Dependencies can also be declared:
[project]
name = "student-project"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
"requests>=2.32",
]
53. Virtual environments
Each project should use its own virtual environment.
Create one:
python3 -m venv .venv
Activate it on Linux/macOS:
source .venv/bin/activate
Activate it on Windows PowerShell:
.venv\Scripts\Activate.ps1
Install dependencies:
python -m pip install requests
54. Why python -m pip?
Using:
python -m pip install requests
makes it explicit which Python interpreter is being used to run pip.
This is useful when multiple Python installations or virtual environments exist.
55. __pycache__ and .pyc files
Python may compile modules to bytecode and store them in:
__pycache__/
For example:
__pycache__/
└── models.cpython-313.pyc
These files should normally not be committed to Git.
A basic .gitignore includes:
.venv/
__pycache__/
*.pyc
56. Example project — Student grade manager
Consider the following project:
grade_manager/
├── pyproject.toml
├── src/
│ └── grade_manager/
│ ├── __init__.py
│ ├── models.py
│ ├── storage.py
│ ├── services.py
│ └── main.py
└── data/
└── students.json
models.py:
from dataclasses import dataclass
@dataclass
class Student:
name: str
grade: float
57. Example project — storage module
storage.py:
import json
from pathlib import Path
from .models import Student
def load_students(
path: Path,
) -> list[Student]:
try:
content = path.read_text(
encoding="utf-8"
)
except FileNotFoundError:
return []
data = json.loads(content)
return [
Student(
name=item["name"],
grade=float(item["grade"]),
)
for item in data
]
def save_students(
path: Path,
students: list[Student],
) -> None:
data = [
{
"name": student.name,
"grade": student.grade,
}
for student in students
]
path.write_text(
json.dumps(data, indent=4),
encoding="utf-8",
)
58. Example project — service module
services.py:
from .models import Student
class EmptyStudentListError(ValueError):
pass
def average(
students: list[Student],
) -> float:
if not students:
raise EmptyStudentListError(
"cannot calculate average "
"for an empty list"
)
return sum(
student.grade
for student in students
) / len(students)
59. Example project — main module
main.py:
from pathlib import Path
from .services import (
EmptyStudentListError,
average,
)
from .storage import load_students
def main() -> None:
path = Path("data/students.json")
students = load_students(path)
try:
result = average(students)
except EmptyStudentListError as error:
print(error)
return
print(f"Average: {result:.2f}")
if __name__ == "__main__":
main()
This example combines:
- modules;
- packages;
- relative imports;
- dataclasses;
- file handling;
- JSON;
- exceptions;
- type hints;
pathlib.
60. Common mistakes
Running package files directly
Suppose main.py contains:
from .services import average
Running:
python src/grade_manager/main.py
may fail because the file is being executed outside its package context.
Instead, from an appropriate project location, run the module:
python -m grade_manager.main
after installing the project or configuring the source path appropriately.
Circular imports
Avoid designs such as:
models.py imports services.py
services.py imports models.py
This usually indicates that responsibilities should be reorganized.
Too much code at module level
Prefer:
def main():
...
if __name__ == "__main__":
main()
instead of placing the entire application directly at module scope.
61. C/C++ habits to reconsider
| C/C++ habit | Typical Python approach |
|---|---|
| separate declaration and implementation files | usually keep a class or function definition in one .py module
|
| include headers | import modules or names |
| manually concatenate path strings | use pathlib.Path
|
| explicitly close files everywhere | use with context managers
|
| use error return codes for expected failures | use exceptions when appropriate |
| create large monolithic source files | split responsibilities into modules and packages |
| rely only on runtime type information | use type hints and static analysis when useful |
| put all source files at project root | use a clear project structure such as src/ and tests/
|
62. Exercise 1 — Split a program into modules
Start from this single-file program:
from dataclasses import dataclass
@dataclass
class Student:
name: str
grade: float
def average(students):
return sum(
student.grade
for student in students
) / len(students)
students = [
Student("Alice", 9.5),
Student("Bob", 8.0),
]
print(average(students))
Split it into:
main.py
models.py
services.py
Use imports rather than duplicating code.
63. Exercise 2 — Build a package
Transform the previous exercise into:
student_app/
├── __init__.py
├── models.py
├── services.py
└── main.py
Requirements:
- use package imports;
- add a
main()function; - use
if __name__ == "__main__"; - run the application as a module.
64. Exercise 3 — Text file processing
Create a program that reads:
grades.txt
with one grade per line.
For example:
9.5
8.0
7.5
10
The program must:
- read all valid grades;
- ignore empty lines;
- report invalid values;
- calculate the average;
- print the highest and lowest grade.
Use:
with;- exception handling;
- type hints.
65. Exercise 4 — pathlib
Write a function:
def list_python_files(
directory: Path,
) -> list[Path]:
...
that returns all .py files in a directory.
Use pathlib, not string concatenation.
Extension: search recursively.
66. Exercise 5 — CSV
Create a CSV file containing:
name,grade
Alice,9.5
Bob,8.0
Carol,7.0
Write functions:
def load_students(path: Path) -> list[Student]:
...
def save_students(
path: Path,
students: list[Student],
) -> None:
...
Use the csv module.
67. Exercise 6 — JSON configuration
Create:
{
"minimum_grade": 5.0,
"maximum_grade": 10.0,
"output_file": "results.txt"
}
Write a Python program that:
- loads the configuration;
- validates the values;
- reports malformed or missing configuration;
- uses the configured output filename.
Use specific exception types where possible.
68. Exercise 7 — Custom exceptions
Create:
class InvalidGradeError(ValueError):
pass
Then write:
def validate_grade(grade: float) -> None:
...
The function should raise InvalidGradeError when the grade is outside the accepted range.
Use it when constructing or loading students.
69. Exercise 8 — Type hints
Add complete type annotations to the following code:
def load_names(path):
with open(path) as file:
return [
line.strip()
for line in file
if line.strip()
]
def find_name(names, target):
for name in names:
if name == target:
return name
return None
The final function should make it clear from its return type that the requested name may not exist.
70. Exercise 9 — Refactor a monolithic application
Consider an application that currently contains:
main.py
and mixes:
- data classes;
- file reading;
- JSON parsing;
- calculations;
- user interaction.
Refactor it into at least:
models.py
storage.py
services.py
main.py
For every module, write one sentence describing its responsibility.
71. Exercise 10 — Mini-project structure
Create the following project:
grade_manager/
├── pyproject.toml
├── README.md
├── .gitignore
├── src/
│ └── grade_manager/
│ ├── __init__.py
│ ├── models.py
│ ├── storage.py
│ ├── services.py
│ └── main.py
└── data/
└── students.json
Requirements:
Studentmust be a dataclass;- storage must use JSON;
- paths must use
pathlib; - invalid grades must raise a custom exception;
- functions must use type hints;
- the program must calculate the class average;
- the main module must not contain storage implementation details.
72. Summary
The main mappings introduced in this laboratory are:
| C/C++ | Python |
|---|---|
| source/header organization | modules and packages |
#include
|
import
|
| namespace | module/package namespace |
main()
|
if __name__ == "__main__"
|
std::ifstream
|
open(..., "r")
|
std::ofstream
|
open(..., "w")
|
| RAII resource cleanup | context manager / with
|
std::filesystem::path
|
pathlib.Path
|
throw
|
raise
|
catch
|
except
|
| custom exception class | subclass of Exception or a more specific exception
|
| static type declaration | optional type annotations |
std::optional<T>
|
None |
std::variant
|
str |
using alias
|
type alias |
| build/project metadata | pyproject.toml
|
The central idea of this laboratory is:
A maintainable Python application is not a single large script. It is organized into modules with clear responsibilities, explicit dependencies, robust error handling and well-defined interfaces.
73. Preparation for Lab 4
Before the next laboratory:
- complete the exercises from this laboratory;
- organize the semester project into multiple modules;
- create a virtual environment for the project;
- add a
.gitignore; - add basic type hints to public functions;
- separate data models, application logic and input/output code;
- ensure that expected errors are handled explicitly.
In Lab 4, the focus will move to testing, debugging and software quality using tools such as pytest, assertions, fixtures, logging and code-quality checks.