Modules and imports
- Lesson 8
- 35 min
- Module 3: Functions and program structure
Lesson 8 of 18 · Python for Practical AI EngineeringModules and imports
1. Computer and environment basics
2. Values and control flow
3. Functions and program structure
4. Files, data and external services
5. Debugging and reliability
6. From notebook to maintainable project
What you will be able to do
Move the ticket functions into a package called ticket_cleaner, and import them into the script. Then two scripts can use the same functions, and the rest of the course adds each new part as its own module.
NotebookDo this lesson in a notebook
The notebook has every step, task and check of this lesson, with the instructions. Its first cells make the ticket-cleaner folder as at the start of this lesson, so it works without your local project. Read the explanations here, and run the code there. It needs no install.
Colab and Kaggle open your own copy. They need a free Google or Kaggle account. Stuck? See the solution notebook.
Before you startWhat to know and to have ready
What you need to know
- Functions with a contract, and a
main()function, from Function contracts. import sysandimport platform, from Install and verify Python: Worked example.
What you need on your computer (only for the local route; the notebook and the boxes on this page need none of it)
count_tickets.pywith the functionsfilter_by_status,count_by_categoryandaverage_priority, and amain()function. You made it in Function contracts: Guided practice. If it is missing or different, replace its contents with the worked example. You can also copy the files from the setup cell of this lesson’s notebook. It has every file as at the start of this lesson.- A terminal in
~/projects/ticket-cleaner. You made the folder in Files and terminal: Guided practice. - The project’s environment
.venv, active in your terminal (the prompt starts with(.venv)). You made it in Isolate dependencies: Guided practice. If it is missing or broken, delete it and make it again from the lock file, as in Make the environment again from nothing.
Tested with Python 3.14.6 on macOS. The notebook was tested in Jupyter.
A module is a Python file
Section titled “A module is a Python file”A module is a file of Python code that other code can import. Every .py file is a module. Its name is the file name without .py: the file report.py is the module report.
You already import modules that come with Python: import sys, import platform. The import statement runs the module’s file once, and then gives you its functions and variables. There are two forms:
import platformprint(platform.python_version()) # module name, dot, function name
from platform import python_versionprint(python_version()) # the function name onlyBoth forms run the same file. The first keeps the module name in front, so the reader sees where python_version comes from. The second is shorter. Use the second form for a few names that you use often.
A package is a folder of modules with a file called __init__.py in it. The __init__.py file tells Python that the folder is a package. It can be empty. You import a module from a package with a dot: from ticket_cleaner.report import count_by_category means “in the package ticket_cleaner, the module report, the function count_by_category”.
The new layout
Section titled “The new layout”At the end of this lesson, the project looks like this:
ticket-cleaner/├── .venv/├── count_tickets.py ← the script that you run├── requirements.txt├── requirements-lock.txt└── ticket_cleaner/ ← the package: code that other code imports ├── __init__.py └── report.py ← filter_by_status, count_by_category, average_priorityThe project folder has a - in its name (ticket-cleaner), and the package has a _ (ticket_cleaner). This is on purpose. A module or package name must be a valid Python name, and a - is not allowed in a Python name.
The script keeps only the part that is specific to this one program: the data and main(). The functions move to report.py, because any program that reports on tickets can use them.
How Python finds a module
Section titled “How Python finds a module”When Python runs import ticket_cleaner, it looks for it in a list of folders, in order:
- The folder of the script that you run. For
python count_tickets.py, that isticket-cleaner/. - The folders of Python’s own modules, such as
sysandcsv. - The packages that you installed in the active environment, such as
httpx.
The first match wins. Two problems come from this order. First, the package must be in the folder of the script that you run, so you run count_tickets.py, which is next to ticket_cleaner/. Second, a file of yours with the same name as a Python module hides that module. The common mistake at the end of this lesson shows this.
Import runs the file
Section titled “Import runs the file”An import runs every line of the module, once. A def line only defines a function, so a module that contains only functions does nothing visible when you import it. A module that has a print or a call at the top level runs it during the import.
Each module has a variable __name__. When you run a file directly, its __name__ is "__main__". When the file is imported, __name__ is the module’s name. Here is a module that prints its name, run in two ways:
print("report.py name:", __name__)python ticket_cleaner/report.pypython -c "import ticket_cleaner.report"report.py name: __main__report.py name: ticket_cleaner.reportThis gives you a way to say “run this only when the file is the program, not when it is imported”:
if __name__ == "__main__": main()This is the main entry point. Put it at the end of every script. Then a test, or a different script, can import the file to use its functions, without running the whole program.
Run Python in your browser
What is __name__ here?
This box runs the code as the main program, as python file.py does. Change the condition to __name__ == "report", and run it. Why does main() not run now?
Tab adds four spaces. Press Esc, then Tab, to leave the box. Ctrl+Enter runs the code.
Worked example: split the script
Section titled “Worked example: split the script”Amira moves the three functions into the package. She leaves the code inside each function unchanged.
"""Clean support-ticket records and summarise them."""def filter_by_status(tickets, status="open"): """Return the tickets that have the given status.""" selected = [] for ticket in tickets: if ticket["status"] == status: selected.append(ticket) return selected
def count_by_category(tickets): """Return the number of tickets in each category, sorted by category.""" counts = {} for ticket in tickets: category = ticket["category"] counts[category] = counts.get(category, 0) + 1 return dict(sorted(counts.items()))
def average_priority(tickets): """Return the average priority, or None when there are no tickets.""" if not tickets: return None total = 0 for ticket in tickets: total = total + ticket["priority"] return round(total / len(tickets), 1)The script now starts with one import line, and ends with the main entry point:
from ticket_cleaner.report import average_priority, count_by_category, filter_by_status
TICKETS = [ {"id": "T-1001", "status": "open", "category": "billing", "priority": 2}, {"id": "T-1002", "status": "closed", "category": "login", "priority": 3}, {"id": "T-1003", "status": "open", "category": "login", "priority": 1}, {"id": "T-1004", "status": "pending", "category": "billing", "priority": 2}, {"id": "T-1005", "status": "open", "category": "billing", "priority": 3}, {"id": "T-1006", "status": "closed", "category": "shipping", "priority": 1},]
def main(): status = "open" selected = filter_by_status(TICKETS, status) print(f"Tickets with status '{status}': {len(selected)}") if not selected: print("No tickets to count.") return for category, count in count_by_category(selected).items(): print(f" {category}: {count}") print(f"Average priority: {average_priority(selected)}")
if __name__ == "__main__": main()The output is the same as before. That is the test of a refactor: the structure changed, and the behaviour did not.
Guided practice: make the package
Section titled “Guided practice: make the package”-
In the
ticket-cleanerfolder, make the package folder:Terminal window mkdir ticket_cleaner -
In VS Code, make
ticket_cleaner/__init__.pywith the one docstring line from the worked example. -
Make
ticket_cleaner/report.py. Move (cut, then paste) the three functions fromcount_tickets.pyinto it. -
In
count_tickets.py, add the import line at the top. Replace the last line,main(), with the main entry point. -
Run the script:
Terminal window python count_tickets.pyOutput Tickets with status 'open': 3billing: 2login: 1Average priority: 2.0 -
List the package folder:
ls ticket_cleaner. You see a new folder,__pycache__. Python saves a compiled copy of each imported module there, so the next import is faster. You do not edit it. If you use Git, add__pycache__/to.gitignore.
Your turn: a second script
Section titled “Your turn: a second script”The help desk lead wants a short report of all urgent tickets, in any status. Make a new script, urgent_report.py, in the project folder. It must:
- Import
filter_by_statusfrom the package. Do not copy the function. - Use the same six tickets.
- For each status (
"open","pending","closed"), print the status and the number of its tickets with priority1. - End with the main entry point.
open: 1 urgentpending: 0 urgentclosed: 1 urgentShow one answer
from ticket_cleaner.report import filter_by_status
TICKETS = [ # the same six tickets as in count_tickets.py]
def main(): for status in ["open", "pending", "closed"]: selected = filter_by_status(TICKETS, status) urgent = [ticket for ticket in selected if ticket["priority"] == 1] print(f"{status}: {len(urgent)} urgent")
if __name__ == "__main__": main()The six tickets are now in two scripts. That is a copy, too. In Module 4, both scripts read the tickets from one data file instead.
Common mistake
A file name that hides a Python module
Symptom: code that worked before stops with an error about a module from Python itself. For example, you made a file called csv.py to practise:
AttributeError: module 'csv' has no attribute 'DictReader' (consider renaming '/Users/amira/projects/ticket-cleaner/csv.py' since it has the same name as the standard library module named 'csv' and prevents importing that standard library module)Cause: Python looks for modules in the folder of your script first. Your file csv.py has the same name as Python’s csv module, so import csv finds your file instead. Your file does not have DictReader. Common names that cause this are csv.py, json.py, random.py, test.py, email.py, and the names of installed packages, such as httpx.py.
Fix:
- Rename your file to a name that describes your code, for example
read_tickets.py. - Delete the
__pycache__folder next to it, so no compiled copy of the old file is left. - Run the program again.
Check your understanding
You import a module, and the terminal shows a report that you did not ask for. What is missing in that module, and where?
Show the answer
The module calls its code at the top level, so the import runs it. Put the call inside if __name__ == "__main__": at the end of the file. Then the code runs only when you run the file directly, not when another file imports it.
Does your answer have the main points of ours?
Sign in and enroll in the course to save your answers. Every check counts toward the certificate.
Check your understanding
Why is the package called ticket_cleaner and not ticket-cleaner, like the project folder?
Show the answer
import ticket-cleaner is not valid Python, because Python reads - as a minus sign. A module or package name must be a valid Python name: letters, numbers and _, not starting with a number. The project folder is never imported, so it can have a -.
Does your answer have the main points of ours?
Sign in and enroll in the course to save your answers. Every check counts toward the certificate.
Recap
- A module is a
.pyfile. A package is a folder of modules with an__init__.pyfile. import moduleandfrom module import nameboth run the module once. Python looks in the script’s folder first, then in Python’s modules, then in the installed packages.- Do not name your files like Python modules or installed packages.
- End every script with
if __name__ == "__main__": main(), so that an import does not run it. - Run scripts from the project folder. Use
python -m package.modulefor a module inside a package.
Next, you give each ticket a proper type with a dataclass, and add type hints that show each function’s contract on its def line.
Next: Structured records