From requirements to a working, tested, and documented program — the complete cycle of real Python development
This is the final chapter of Programming with Python. Everything you have learned — variables, conditionals, loops, functions, data structures, files, exceptions, classes, and code quality — comes together here. Your goal is to build one complete Python program from start to finish: planned, implemented, tested, improved, documented, and presented.
You have already written dozens of short programs. You have traced variable values, fixed bugs, designed functions, processed files, and organized classes. Now the question changes from can you write a program that does X to can you build a program worth using. That shift — from exercising a skill to building something real — is one of the most important transitions in a programmer's development.
A short program proves you understand a concept. A complete project proves you can combine many concepts into something that works as a whole, handles unexpected input, survives a restart, can be understood by someone else, and improves when you find its weaknesses. This chapter teaches you how to do all of that — systematically.
By the end of this chapter, you should be able to:
__name__csv; refactoring for clarity; what not to over-engineerYou have already written dozens of short programs. You have traced variable values, fixed bugs, designed functions, processed files, and organized classes. Now the question changes from can you write a program that does X to can you build a program worth using.
A good beginner Python project has five qualities.
A focused, working, readable program is far more impressive than an ambitious, broken, incomprehensible one. Scope down aggressively on a first capstone project. You can always extend it after it works.
Section 30.10 lists several project options at different levels. Before choosing, ask yourself three questions.
What problem do I actually care about? The best projects come from genuine frustration or genuine curiosity. If you actually want a program to track your reading list, you will be more motivated to finish it than if you picked a project randomly.
How much time do I have? A realistic project for two to three weeks of focused work is very different from a one-weekend sprint. Section 30.4 discusses milestones. Estimate how many working sessions you have, then choose a scope that fits.
Do I understand what the program needs to do before I start writing code? If you cannot describe the program's purpose, inputs, outputs, and data storage in plain language, you are not ready to write a line of Python yet. Take time to think before you open your editor.
Choosing a project that depends on external packages you do not know yet. Trying to build a mobile app, website, or graphical application. Designing a project with no data storage — programs that only print menus and do arithmetic are not real applications. Starting to code before you have a clear description of what the program does.
Requirements are plain-language descriptions of what your program must do. They come before code. Writing them down forces you to think clearly and gives you a checklist to test against.
There are two levels of requirements useful for a beginner project.
Functional requirements describe what the program does. They answer: what can the user do with this program?
Non-functional requirements describe how the program behaves. They answer: how does the program handle bad input? what happens when the file is missing? should the program be fast, simple, or readable?
Write requirements as numbered statements. Use plain language. Write what the program must do, not how it does it — you will figure out how during planning and implementation. After writing your requirements, read them aloud. Ask: if someone built exactly what is written here, would it be useful?
A milestone is a point in development where your program can do something real and testable. Milestones work against a common beginner trap: trying to build everything at once and having nothing working for weeks. If you hit a wall, you want to be able to step back to the last working version, not rebuild from zero.
A good beginner project has three to five milestones.
| Milestone | Goal | Done When |
|---|---|---|
| 1 | Data structure for a session, add_session() function | One session can be created and printed |
| 2 | Menu loop with all options listed | User can navigate menu; program exits cleanly |
| 3 | All features working in memory | Log, view summary, view subject, view recent all work |
| 4 | File saving and loading | Sessions survive program restart |
| 5 | Polish | Clean output, docstrings, edge cases handled |
Write your own milestone table before writing a single line of code. It is a five-minute investment that will save hours of confusion later.
Good programs are designed twice: once in words, once in code. The design in words is faster, cheaper to change, and easier to share. Never skip it.
Ask: what information does this program need to store? For the Study Tracker, each session needs a subject name, a number of minutes, an optional note, and the date it was logged. A single session is a dictionary:
# A single session as a dictionary session = { "subject": "Mathematics", "minutes": 45, "note": "Practiced integration by parts", "date": "2025-09-14" } # All sessions together are a list of these dictionaries sessions = [ {"subject": "Mathematics", "minutes": 45, "note": "...", "date": "2025-09-14"}, {"subject": "Python", "minutes": 60, "note": "...", "date": "2025-09-14"}, {"subject": "Mathematics", "minutes": 30, "note": "...", "date": "2025-09-15"} ]
This is the central data structure. Sketch it in plain language and then as Python before writing any other code.
List every function you expect to write. Give each one a name, its parameters, what it returns or does, and one sentence describing its job.
| Function | Parameters | Returns | Job |
|---|---|---|---|
add_session | sessions, subject, minutes, note | Updated sessions list | Creates and appends a session dictionary |
show_summary | sessions | Nothing (prints) | Prints total minutes per subject |
show_subject_sessions | sessions, subject | Nothing (prints) | Prints all sessions for one subject |
show_recent_sessions | sessions, n | Nothing (prints) | Prints the last n sessions |
save_sessions | sessions, filename | Nothing | Saves sessions to a CSV file |
load_sessions | filename | sessions list | Loads sessions from CSV, returns list |
get_valid_minutes | None | Integer | Prompts user until a valid positive integer is entered |
run_menu | sessions | Nothing | Displays menu, handles user choices |
This function table is your implementation plan. Build one function at a time. Test each one before moving to the next. When you are building from a plan, you always know what you are doing next. Without a plan, you invent the structure as you go — and invented structure is usually confused structure.
The first working version does one thing: it proves the core idea works. It does not need to be polished, complete, or well-documented. It needs to be real, runnable code you can build on.
def add_session(sessions, subject, minutes, note=""): """Add a new study session to the sessions list.""" from datetime import date session = { "subject": subject, "minutes": minutes, "note": note, "date": str(date.today()) } sessions.append(session) return sessions # Quick test — run this before adding anything else sessions = [] sessions = add_session(sessions, "Mathematics", 45, "Integration practice") sessions = add_session(sessions, "Python", 60, "Working on capstone") print(sessions)
Run this and make sure it works before continuing. Every new function should be tested in isolation before being connected to the menu loop.
A stub function has the right name and parameters but does not do anything real yet. It lets you run and test the menu before every feature is finished. Replace stubs one at a time.
def get_valid_minutes(): """Prompt user until a valid positive integer is entered.""" return 30 # Stub: always returns 30 for now def show_subject_sessions(sessions, subject): """Print all sessions for a given subject.""" print("(Not yet implemented)\n") # Stub def show_recent_sessions(sessions, n=5): """Print the most recent n sessions.""" print("(Not yet implemented)\n") # Stub def save_sessions(sessions, filename): """Save sessions to a CSV file.""" print(f"(Stub: would save {len(sessions)} sessions to {filename})\n") def load_sessions(filename): """Load sessions from a CSV file.""" return [] # Stub: always returns empty list
if __name__ == "__main__": sessions = load_sessions("study_sessions.csv") run_menu(sessions)
if __name__ == "__main__":Protecting your main code with this guard is good Python practice. It ensures that importing your file in a test or another module does not accidentally run the program. Every Python script that is meant to be run directly should have it.
Testing a full program is different from testing a single function. You need to test individual pieces and also how they work together.
# Test add_session sessions = [] sessions = add_session(sessions, "History", 30, "Read chapter 4") assert len(sessions) == 1 assert sessions[0]["subject"] == "History" assert sessions[0]["minutes"] == 30 print("add_session: PASSED") # Test show_summary with known data sessions = [] sessions = add_session(sessions, "History", 30) sessions = add_session(sessions, "History", 45) sessions = add_session(sessions, "Python", 60) # Expected: History = 75 minutes, Python = 60 minutes show_summary(sessions) # Read the output and verify manually
Test Case — get_valid_minutes | Input | Expected Behavior |
|---|---|---|
| Normal case | "45" | Returns 45 |
| Zero | "0" | Re-prompts (zero is not a valid session length) |
| Negative | "-10" | Re-prompts (negative is invalid) |
| Non-numeric | "abc" | Re-prompts (not a number) |
| Empty string | "" | Re-prompts (empty input) |
| Large valid value | "120" | Returns 120 |
Test these scenarios explicitly, not casually.
Every time you add a feature or fix a bug, re-run your earlier tests. A fix in one place sometimes breaks something that worked before. This is called a regression. Testing old behavior after new changes is called regression testing. It takes a few minutes and saves a lot of pain.
Once all your core features work and pass testing, improve the program. Improvement happens in two directions: completing stubs with real implementations, and refactoring existing code to be cleaner.
get_valid_minutesdef get_valid_minutes(): """Prompt the user until a valid positive integer is entered. Returns: int: A positive number of minutes. """ while True: raw = input("Minutes studied: ").strip() try: minutes = int(raw) if minutes <= 0: print("Please enter a positive number of minutes.") continue return minutes except ValueError: print("Please enter a whole number.")
save_sessions and load_sessionsimport csv def save_sessions(sessions, filename): """Save all sessions to a CSV file. Args: sessions (list): The list of session dictionaries to save. filename (str): Path to the output CSV file. """ fieldnames = ["subject", "minutes", "note", "date"] try: with open(filename, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() writer.writerows(sessions) except OSError as e: print(f"Error saving sessions: {e}") def load_sessions(filename): """Load sessions from a CSV file. Args: filename (str): Path to the CSV file to load. Returns: list: A list of session dictionaries, or an empty list if file not found. """ sessions = [] try: with open(filename, newline="", encoding="utf-8") as f: reader = csv.DictReader(f) for row in reader: row["minutes"] = int(row["minutes"]) # CSV stores strings sessions.append(row) except FileNotFoundError: pass # Normal on first run except OSError as e: print(f"Error loading sessions: {e}") return sessions
| Refactoring Target | What to Do |
|---|---|
| Remove duplication | If two functions do something very similar, extract a shared helper |
| Improve output formatting | Align columns; use consistent spacing; add blank lines between sections |
| Simplify complex conditions | Extract a hard-to-read if statement into a Boolean variable with a clear name |
| Improve variable names | Rename s to session, m to minutes — unless the short form is genuinely clearer in context |
| Improve error messages | Every error message should tell the user what went wrong and what to do |
There is a trap called premature optimization: spending time improving things that are not problems. At the beginner stage, rewriting working code in a cleverer way with no benefit, adding features not in your requirements, and redesigning a data structure that already works are all ways to not finish. Finish the program you planned. Then improve it.
Documentation explains your program to someone who did not write it — and to yourself six months from now. A complete beginner project has three levels of documentation.
Every function should have a docstring. A docstring is a string literal immediately after the def line. It explains what the function does, what its parameters mean, and what it returns.
def add_session(sessions, subject, minutes, note=""): """Add a new study session to the sessions list. Args: sessions (list): The existing list of session dictionaries. subject (str): The name of the subject studied. minutes (int): The number of minutes spent studying. note (str): An optional note about the session. Defaults to "". Returns: list: The sessions list with the new session appended. """ from datetime import date session = { "subject": subject, "minutes": minutes, "note": note, "date": str(date.today()) } sessions.append(session) return sessions
Use inline comments for anything not obvious from the code itself. Do not comment obvious things.
# Good: explains why, not what recent.reverse() # Most recent sessions should appear first row["minutes"] = int(row["minutes"]) # CSV stores all values as strings # Bad: restates the code with no added meaning count += 1 # Add 1 to count
Every project should have a README.md file in the same folder as the program. A good README has a one-sentence description, how to run the program, how to use it, what files are included, known limitations, and author and date.
python study_tracker.py. No additional packages required.study_tracker.py — Main program. study_sessions.csv — Auto-generated data file (created on first save).Writing docstrings and a README also forces you to articulate what your functions actually do. Sometimes you discover you cannot explain a function clearly — which means the function itself is unclear. Documentation is not just for readers; it improves the code it describes.
Whether you present in class or submit for grading, you should be ready to walk someone through your program and explain your decisions. A good project presentation covers five areas.
Use a font large enough to read from a distance. Highlight the section you are discussing. Read key lines aloud: "This line opens the file for reading." Explain why you made design choices, not just what the code does.
Take time to look back at your first programs from Chapter 3 and Chapter 4. Compare them to what you have just built. The difference is not only in size or complexity. It is in how you think.
At the beginning of this course, programming probably felt like solving a puzzle by guessing. Now it should feel more like engineering: designing, planning, building in stages, testing deliberately, reading your own code with critical eyes, and improving it systematically.
| Habit | What It Means Now |
|---|---|
| You read code before you write it | You can trace variable values, predict output, and spot bugs in code you did not write. This is one of the most valuable programming skills there is. |
| You design before you type | You know how to write requirements, sketch a data structure, list your functions, and plan program flow on paper before opening your editor. |
| You test with intention | You know the difference between testing casually and designing test cases. You know what edge cases are and why they matter. |
| You debug systematically | You read error messages instead of panicking. You use print-style debugging. You change one thing at a time. |
| You organize code to communicate | Your functions have names that explain what they do. Your docstrings tell the next programmer — and future you — what each piece does. |
These habits matter more than any particular piece of syntax. Python itself will change. New versions will add features. Libraries will come and go. The habits of a careful, methodical programmer transfer across every language, tool, and project.
pathlib, os, shutil, and scripting tools that automate real file and system tasks.Programming is not memorizing syntax. It is the disciplined practice of expressing ideas as precise instructions, testing those instructions against reality, and improving them until they work. You have been practicing that discipline throughout this book.
Keep building.
if __name__ == "__main__":.def statement to document a function's purpose, parameters, and return value.README.md, explaining what a program does and how to use it.Use this template before you write a single line of Python. Fill it in on paper or in a text file.
Project Title: One-sentence description: Functional Requirements: 1. 2. 3. 4. 5. Non-Functional Requirements: 1. 2. 3. Central Data Structure (plain language, then sketch as Python): Function List: | Function Name | Parameters | Returns | Job | |---------------|------------|---------|-----| | | | | | Program Flow (numbered steps): 1. 2. 3. ... Milestone Plan: | Milestone | Goal | Done When | |-----------|------|-----------| | 1 | | | | 2 | | | | 3 | | | | 4 | | | | 5 | | |
Choose one project appropriate to your available time and interest. Build the core scope first — extend only after it works.
These questions draw on skills from across the entire textbook. Suitable for final exam preparation, independent review, or instructor-assigned exercises.
with statement do when opening a file?try and except do?Read each program carefully and answer the question below it.
data = [12, 7, 19, 3, 15] result = 0 for value in data: if value > 10: result += value print(result)
What does this program print? What would you rename result to better describe what it holds?
def mystery(text): count = 0 for char in text: if char in "aeiouAEIOU": count += 1 return count print(mystery("Programming")) print(mystery("Python"))
What does mystery do? What is a better name for it? What does it print?
records = [
{"name": "Alice", "score": 88},
{"name": "Bob", "score": 74},
{"name": "Carol", "score": 95}
]
for record in records:
if record["score"] >= 90:
print(record["name"], "passed with distinction")
elif record["score"] >= 75:
print(record["name"], "passed")
else:
print(record["name"], "did not pass")
What does this program print? What would happen if the list were empty?
def load_data(filename): items = [] try: with open(filename) as f: for line in f: items.append(line.strip()) except FileNotFoundError: pass return items
What does this function return if the file exists? What does it return if the file does not exist? Why is pass used in the except block?
Each code sample contains at least one error. Identify the error and write a corrected version.
def total(numbers) result = 0 for n in numbers: result += n return result
scores = [85, 90, 78, 92] average = sum(scores) / len(scores) print("Average: " + average)
result = is_passing(75) if result = True: print("Passing")
counts = {}
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
for word in words:
counts[word] = counts[word] + 1
print(counts)
clean_name(name) that takes a string, strips whitespace from both ends, and returns the name with the first letter of each word capitalized.find_above_average(numbers) that takes a list of numbers and returns a new list containing only the values above the average. If the list is empty, return an empty list.count_by_category(items) that takes a list of dictionaries, each with a "category" key, and returns a dictionary mapping each category to the number of items in it.safe_divide(a, b) that returns the result of a / b but returns None if b is zero. Write three test calls demonstrating normal use, zero division, and using the result in a print statement.load_words(filename) that reads a text file where each line is one word, and returns a list of those words with whitespace stripped. If the file is not found, print a message and return an empty list.Write answers to these questions in your notebook or study journal before moving on.
You have completed Programming with Python. Here is every major skill developed across all seven parts.
| Part | Major Skills |
|---|---|
| Part I | Running Python, values and variables, expressions, arithmetic, input and output, simple programs, comments |
| Part II | Booleans, comparisons, conditionals, while loops, for loops, code tracing, debugging, test cases |
| Part III | Defining and calling functions, parameters, return values, scope, decomposition, modules, standard library |
| Part IV | Strings, lists, tuples, sets, dictionaries, nested data structures, choosing the right structure |
| Part V | Text files, CSV files, exceptions, dates, randomness, simulation, automation scripts |
| Part VI | Classes, object state, methods, application design, testing, refactoring, code quality |
| Part VII | Project planning, requirements, milestones, data design, implementation, testing, documentation, presentation |
This textbook brought you from running your first Python script to building a complete, documented, tested program. You have learned to read code, design before coding, test deliberately, debug systematically, and write clearly. Those habits belong to you now — carry them into everything you build next.