Why this chapter matters
Well-scoped functions reduce bugs, and module boundaries make code easier to test, maintain, and reuse.
What you will learn
- Define functions with parameters and return values.
- Organize related logic into importable modules.
- Use __name__ == '__main__' to separate script execution from reusable code.
Understand the core ideas
Functions let you turn long scripts into small units with clear inputs and outputs. In automation work, this is the difference between code you can test and code you can only hope behaves correctly. A function such as parse_amount(text) should do one task and return one well-defined result or raise a clear exception. When function names map to business operations, your code becomes readable as a workflow. Small pure functions also reduce accidental coupling because they depend on arguments, not on mutable global state.
Modules package those functions into reusable files. Good module boundaries make import behavior predictable and prevent side effects at import time. Place executable script orchestration in a main function, then call it only under if __name__ == '__main__'. That guard allows tests to import helper functions without running file writes, API calls, or other operational actions. Error handling should live near boundaries. For example, a network module can translate low-level request exceptions into a domain-friendly error that upstream code can log and handle consistently.
Key terms
- function contract
- A clear definition of what inputs are accepted and what output or error is produced.
- side effect
- An external change such as file writing, printing, or network calls beyond returning a value.
- module boundary
- A separation point where related logic is grouped into an importable file.
- entry point guard
- The if __name__ == '__main__' pattern that prevents script execution on import.
Refactor a report script into testable modules
A single file currently loads CSV data, normalizes channels, computes totals, and writes output. You need a structure that supports unit tests and safer failure handling.
- Create functions in transform.py: parse_amount, normalize_channel, and summarize_revenue. Each function should accept explicit inputs and return deterministic outputs.
- Create io_ops.py for read_orders_csv and write_summary_csv. Keep these functions focused on file boundaries and raise clear errors for missing files or required columns.
- Create main.py with run_daily_report(input_path, output_path) that calls io and transform functions in order, catches boundary errors, and logs a concise failure reason.
- Wrap execution with if __name__ == '__main__' and call run_daily_report with arguments so imports in tests do not trigger processing.
A common misconception
Claim: Using more functions always makes code harder because you must jump between files.
Correction: Well-scoped functions reduce mental load by isolating purpose. Navigation cost is lower than debugging one large script with mixed concerns and hidden dependencies.
Lessons in this chapter
- Function signatures and returnsDesign small functions that perform one clear task.
- Scope and stateAvoid hidden dependencies by passing data explicitly.
- Module importsImport reusable functions without side effects.
- Guide: functions and modulesRefactor one long script into reusable files. Read the full guide →
Study task
Chapter checkpoint
What does if __name__ == '__main__' protect against?
It prevents script-only code from running when the file is imported as a module in another file or test.