Modules and Program Structure
Learning Objectives
- You can split a program into modules and import your own functions and classes.
- You can explain what runs during import and use a guarded entry point.
- You can run tests without launching an interactive application.
- You can organize imports around user interface, model, and storage responsibilities.
Part 5 put a translation rule in its own source file and used it from an application and a check file. We now apply that pattern to classes, then follow calls between the parts of a larger application.
Choosing classes and choosing files are separate decisions. A module can contain functions, classes, or both. Group related work so another file can use it without starting the whole application.
Import a Class from Another File
The editor below contains three files. Select counter_app.py and Run; it prints 6. Then run check_counter.py to check two independent counters.
from models import Counter makes the class available under the name Counter in the importing file. The filename in the import has no .py suffix. The application and checks use the same class definition; moving it into a module does not change what its instances do.
You could instead write import models, then create a counter with models.Counter(5). Use the name that matches your import. Avoid wildcard imports such as from models import *, and choose filenames that do not hide standard-library modules such as json.py or csv.py.
Importing and Creating an Instance Are Separate
When Python first imports models, it runs the file’s top-level statements, including class Counter. Python executes the class body to define the methods; their bodies do not run at this point.
Calling Counter(5) creates an instance and runs __init__ for that instance.
Later ordinary imports in the same Python process reuse the loaded module. An unguarded main() call, input(...), or graphical launch at the top level would also run during the first import.
Recall the guard from Part 5:
if __name__ == "__main__": main()It runs main() when the file is executed directly and skips that call when the file is imported by its module name. Other top-level statements still run. That is why check_counter.py prints only Checks passed. even though it imports the application too.
Trace a module containing a class. Distinguish statements that run during import from statements that run when an instance is created.
Now move a working class into its own module. Preserve its attributes and query method, and use the imported class from both the application and the checks. Use your IDE and a separate folder, following the overview’s workflow. Run check.py and main.py separately to compare importing with direct execution. The Inline Editor is also available for these checks.
Move a class into a module
0 / 20 points
Move the supplied StudySession class from main.py into models.py, then import it in main.py. Keep one class definition, in models.py. You may import the class by name or import the module and use models.StudySession.
Preserve the class’s behavior. StudySession(subject, minutes) receives a valid subject string and positive integer minutes, storing both as attributes. Keep the subject exactly as supplied, including whitespace or an empty string. is_long() returns whether the instance’s current minutes are at least 60, without changing the instance, printing, or reading input. Separate instances remain independent.
Keep main() in main.py. It creates StudySession("History", 30) using the imported class and prints exactly Long session: False, using that instance’s is_long() result. Running main.py calls main() once. Preserve the guarded entry point; importing main or models must not print, prompt, or start the application.
The starter supplies a working application and an empty models.py. Run main.py before moving the class to see the behavior to preserve. The supplied check.py already imports StudySession from models; it will run after the move is complete. It checks the 60-minute boundary, changing an instance’s minutes, and independent instances. Keep the application and check file using the same class from models.py.
Work in your IDE
An IDE is recommended for this exercise. Download it with the IDE plugin or choose Download starter code in the File Submission tab. Extract the files into a separate exercise folder and open that folder in your IDE, using the environment from Part 5.
From that folder, run py check.py on Windows or python3 check.py on macOS/Linux; use python instead if that is your configured command. After moving the class, run both check.py and main.py again. The checks should print only Checks passed., and the application’s output should match its original output. Extend the sample checks with cases from the contract.
Submit with the IDE plugin or upload a ZIP in the File Submission tab. Include the supplied source and sample files and your checks at the archive’s top level. Leave out generated saves, temporary test data, .venv, and __pycache__.
You can also use the Inline Editor tab. Open Show file tree to switch between the three files. Run executes the selected file; Submit sends the editor files for grading.
Next, protect an interactive program so another file can import its definitions without starting its prompts. This module contains a function; the same import and startup rules apply.
Prepare a session module
0 / 15 points
Repair the starter so it can serve as both an importable module and a runnable program. Keep total_minutes(sessions) available to callers: it returns the total of nonnegative integer durations, or 0 for an empty list, without printing or changing the list.
Importing main must not prompt or print. Running main.py must call main() once, ask exactly Minutes: , read a nonnegative integer, and print Total: N, where N is the total of that duration and 10. main() must obtain that total through total_minutes. Inputs are valid in this exercise.
Use check.py to try importing the module. Keep the documented behavior while correcting how execution begins.
Work in your IDE
An IDE is recommended for this exercise. Download it with the IDE plugin or choose Download starter code in the File Submission tab. Extract the files into a separate exercise folder and open that folder in your IDE, using the environment from Part 5.
From that folder, run py check.py on Windows or python3 check.py on macOS/Linux; use python instead if that is your configured command. Run main.py separately after the import check; it should ask for minutes exactly once. Extend the sample checks with cases from the contract.
Submit with the IDE plugin or upload a ZIP in the File Submission tab. Include the supplied source and sample files and your checks at the archive’s top level. Leave out generated saves, temporary test data, .venv, and __pycache__.
You can also use the Inline Editor tab. Open Show file tree, select check.py, and Run. Run executes the selected file; Submit sends the editor files for grading. Generated files are temporary, so any browser save/load check must happen within one execution.
Follow a Command Across Modules
The equipment library will use three modules for its application logic:
| Module | Responsibility |
|---|---|
app.py | Read commands and present their results to the user |
models.py | Define the items, library, and their rules |
storage.py | Convert between files and library instances |
For a borrow command, app.py reads an identifier and calls library.borrow_item(item_id). In models.py, that method finds the item and calls its borrow() method. For an available item, True returns through the library method to app.py, which prints Item borrowed.. Repeating the command returns False, so the user interface prints Cannot borrow item.. Neither model method chooses the message.
For a save command, app.py calls save_library(path, library) in storage.py. The storage function asks the model for dictionary records and writes them as JSON. If it returns normally, app.py prints Library saved.; if it raises, the user interface reports the failure. The next chapter develops the record conversion and file operations. The model itself does not ask for a path or open a file.
Before continuing, identify which module would change if the success message changed, then which would change if the saved file format changed.
Dependencies Should Have a Direction
A module depends on another module when it uses definitions imported from it. For this application, the arrows show which module imports which:
app.py -> models.pyapp.py -> storage.pystorage.py -> models.pyThe model does not import the user interface. The storage module does not read menu commands. You can therefore test the library rules and file operations without starting an interaction.
A circular import occurs when modules depend on one another in a cycle. It can make code try to use definitions before the other module has finished loading. Move common work to a module both can use, or change which module coordinates the operation, instead of copying the same rules into both files.
The concluding application also has separate files for launching and checking the program. A file per class is not a requirement: LoanItem and EquipmentLibrary can both belong in models.py.
Check Your Understanding
- How does importing
Counterdiffer from callingCounter(5)? - Which code executes when a module is first imported?
- What does the main guard prevent, and what does it not prevent?
- Which modules take part in a borrow command and a save command, and which one presents the result?
- Why can moving one responsibility fix an import cycle more cleanly than copying its code?