Python is the glue in many AI applications.
The AI model may produce the answer. Python handles the work around it. Python receives the user’s text, cleans it, sends it to the model, checks the reply, and decides what happens next.
You do not need to know all of Python before you can build something useful. You need a small set of ideas and a clear picture of how data moves through them.
Lesson details
- Module
- AI engineering foundations
- Lesson
- 1 · Python foundations for AI applications
- Difficulty
- Beginner
- Learning time
- 32 minutes
Before you start
- You can use a web browser
- You have completed How to use this course
- No previous Python is required
Skills you will gain
- Store text in variables and lists
- Read a small Python function
- Clean a group of document strings
- Recognise and fix a shared-list default bug
- Use type hints and dataclasses to describe AI application data
- Choose a comprehension or generator for data processing and streaming
- Add shared behaviour with decorators and cleanup with context managers
- Handle expected errors, log safely, and organise a small AI project
Why this matters
AI applications receive untidy data. A support message may contain extra spaces. A document list may contain blank entries. A model should not have to guess which text is useful.
Python lets you turn that messy input into a predictable shape before another part of the system uses it.
Start with four small building blocks
A variableVariableA name that points to a value in a program. In `message = "hello"`, the name `message` lets you use that text again later. is a name that points to a value.
message = " Where is my order? "
The name is message. The value is the text inside the quotation marks.
A listListAn ordered Python collection that can hold several values. Lists use square brackets, such as `["one", "two"]`. keeps several values in order. Python lists use square brackets:
messages = [" Refund please ", " ", "Where is my order?"]
A loop repeats an instruction. A for loop can visit each item in a list, one at a time.
A functionFunctionA named, reusable set of instructions. A function can receive input through parameters and return a result. is a named, reusable group of instructions. A function can receive an input and return an output.
See the whole idea
The diagram contains three stages:
- Input: a list holds three pieces of text.
- Process: the function removes surrounding spaces and ignores text that becomes empty.
- Output: a new list holds only useful text.
The original input stays separate from the result. This makes the function easier to reason about.
Follow the steps
Step 1: receive the input
The function receives a list through a parameter. A parameter is the name a function uses for an input.
def clean_messages(messages):
Here, messages is the parameter.
Step 2: make an empty output list
cleaned = []
The function needs somewhere to collect its results. It starts with an empty list.
Step 3: visit each message
for message in messages:
On each pass through the loop, message points to one value from the input list.
Step 4: clean and check the text
tidy = message.strip()
if tidy:
The string method .strip() removes spaces from the beginning and end.
An if statement runs its indented code only when a condition is true. An empty string counts as false, so if tidy: rejects blank text.
Step 5: return the result
cleaned.append(tidy)
return cleaned
.append() adds one value to a list. return sends the finished list back to the code that called the function.
Worked example
Trace this input by hand:
[" Refund please ", " ", "Where is my order?"]
| Loop pass | Current value | After .strip() |
Keep it? | Output so far |
|---|---|---|---|---|
| 1 | " Refund please " |
"Refund please" |
Yes | ["Refund please"] |
| 2 | " " |
"" |
No | ["Refund please"] |
| 3 | "Where is my order?" |
unchanged | Yes | ["Refund please", "Where is my order?"] |
Nothing mysterious happens between input and output. The function applies the same two checks to every item.
Run the code
def clean_messages(messages):
cleaned = []
for message in messages:
tidy = message.strip()
if tidy:
cleaned.append(tidy)
return cleaned
raw_messages = [" Refund please ", " ", "Where is my order?"]
print(clean_messages(raw_messages))Important lines
def clean_messages(messages):- Creates a reusable function and names its input messages.
cleaned = []- Creates a fresh output list each time the function runs.
for message in messages:- Visits every input string, one at a time.
tidy = message.strip()- Removes spaces from both ends without changing spaces inside the sentence.
if tidy:- Runs the next line only when some text remains.
return cleaned- Sends the completed list back to the caller.
Now run it in your browser. Change one message. Add an empty string. Predict the output before selecting Run.
A bug worth remembering
The original lesson opened with a Python trap that has appeared in real software:
def add_message(message, batch=[]):
batch.append(message)
return batch
print(add_message("first"))
print(add_message("second"))
The surprising output is:
['first']
['first', 'second']
Python creates the default list once, when it defines the function. Later calls reuse that same list.
Use None as the default, then create a new list inside:
def add_message(message, batch=None):
if batch is None:
batch = []
batch.append(message)
return batch
This matters in an AI system because shared state can mix data from separate requests.
Part 2 — Python patterns for reliable AI applications
The first part gives you the language basics. These patterns make a small AI application easier to change, test, and debug.
Type hints say what data is expected
Type hintsType hintsOptional annotations that declare the expected types of variables, arguments and return values (e.g. `def f(x: int) -> str`). Python does not enforce them at runtime, but tools like mypy, your editor, and libraries like Pydantic read them to catch bugs before the code ever runs. are labels for expected data shapes.
def clean_documents(documents: list[str]) -> list[str]:
return [document.strip() for document in documents if document.strip()]
The function expects a list of strings and returns a list of strings. Python does not enforce this at runtime, but editors, type checkers, and validation libraries can spot mismatches early. Hints do not replace validation for untrusted user or API data.
Dataclasses group related values
A dataclassDataclassA class defined with the `@dataclass` decorator, which auto-generates the boilerplate — `__init__`, `__repr__`, equality — from typed field declarations. The idiomatic way to model a bundle of configuration or structured data without writing constructors by hand. is a lightweight way to name related configuration values.
from dataclasses import dataclass, field
@dataclass
class ChunkConfig:
size: int = 300
overlap: int = 40
sources: list[str] = field(default_factory=list)
config = ChunkConfig(sources=["handbook.md"])
print(config)Important lines
@dataclass- Creates useful initialisation and display behaviour from declared fields.
size: int = 300- Names an integer field and its default value.
field(default_factory=list)- Creates a fresh list for every object, avoiding the shared mutable-default bug.
Comprehensions are short, visible transformations
A comprehensionComprehensionA compact Python expression that constructs a list, set, or dictionary by mapping and optionally filtering items. It is best for one clear transformation, not a long hidden workflow. builds a collection in one clear expression.
titles = [document.title() for document in documents if document.strip()]
Read it left to right: produce a title for each non-blank document. Use a comprehension for one obvious map or filter. Use a normal loop when steps need names, error handling, logging, or more than one decision.
Generators stream one value at a time
A generatorGeneratorA function that uses `yield` to produce values one at a time, on demand, instead of building a whole list in memory. The consumer pulls the next value when it wants it; the generator pauses in between. It is how you stream LLM tokens as they arrive and how you process datasets too large to fit in memory. uses yield to pause and produce each value only when the next consumer asks for it.
def non_blank_lines(path):
with open(path, encoding="utf-8") as file:
for line in file:
tidy = line.strip()
if tidy:
yield tidy
Lists are eager: they build every value now. Generators are lazy: they make the next value when needed. This suits large files, document pipelines, and streaming model tokens. Generators are single-use; make a new generator to iterate again.
Decorators add shared behaviour around a function
A decoratorDecoratorA function that wraps another function to add behaviour around it — retries, timing, caching, logging — without changing the wrapped function's own code. Applied with the `@decorator` syntax on the line above a `def`. The standard way to attach cross-cutting concerns to the many API calls in an AI system. wraps a function to add timing, retries, rate limiting, or logging.
from functools import wraps
from time import perf_counter
def timed(function):
@wraps(function)
def wrapper(*args, **kwargs):
start = perf_counter()
result = function(*args, **kwargs)
print("seconds:", round(perf_counter() - start, 3))
return result
return wrapper
Keep decorators small. Too many stacked wrappers hide control flow and make debugging difficult. Retry only errors you expect and can safely repeat.
Context managers guarantee cleanup
A context managerContext managerAn object used with a `with` block that guarantees setup and teardown run around a piece of code — even if it raises. `__enter__` runs on the way in, `__exit__` on the way out, always. Used to open and reliably close files, network clients, database connections and timers. is used with with. It guarantees cleanup even if code inside fails.
with open("ticket.txt", encoding="utf-8") as file:
ticket = file.read()
# file is closed here, even if read() raised an exception
Use context managers for files, database transactions, network clients, locks, and timing scopes.
Handle expected errors and log without secrets
Exception handlingException handlingCatching a specific expected runtime error, recovering or adding useful context, and allowing unknown or important errors to remain visible. It prevents a known failure from crashing an entire workflow without hiding bugs. catches a specific expected error. Do not use a bare except, which catches everything and can hide programming mistakes.
import logging
logger = logging.getLogger(__name__)
def parse_limit(raw_limit: str) -> int:
try:
limit = int(raw_limit)
except ValueError as error:
raise ValueError("limit must be a whole number") from error
logger.info("parsed document limit: %d", limit)
return limit
LoggingLoggingRecording structured operational events such as request counts, warnings, errors, and timing so a system can be observed and debugged. Logs must not contain secrets, API keys, or unnecessary personal data. records operational events. Prefer it to print in application code. Use placeholders so expensive values are not formatted unless needed. Never log API keys, passwords, full private prompts, or personal data unless a narrowly justified policy permits it.
Give the project a home for each concern
A project layoutProject layoutA deliberate organisation of source code, tests, configuration, and documentation so an application is easy to navigate, import, test, and deploy consistently. prevents one giant file from becoming the whole application.
support_assistant/
├── pyproject.toml
├── README.md
├── src/support_assistant/
│ ├── config.py
│ ├── clients.py
│ ├── pipeline.py
│ └── logging.py
└── tests/
Keep configuration, external clients, domain pipeline logic, and tests separate. A src layout also makes accidental imports from the working directory less likely.
Real-world AI engineering use
The cleaning function is not artificial intelligence. It is software engineering around the model. AI engineering needs both.
Common mistakes
Changing the input list while looping over it.
Fix — Build a separate output list. Removing items from the list you are visiting can make Python skip the next item.
Forgetting to return the result.
Fix — Add return cleaned after the loop. Without return, Python gives the caller None.
Putting return inside the loop.
Fix — Align return with the for line. If it is inside the loop, the function stops after the first message.
Using an empty list as a default parameter.
Fix — Use None and create a fresh list inside the function so separate calls do not share data.
Knowledge check
Choose an answer. Feedback appears immediately and explains why.
Knowledge check
Practice activity
Practice · 10–20 minutes
Prepare feedback for a sentiment model
Goal: Write a function that cleans feedback strings and keeps only useful entries.
- Start with the code below.
- Create an empty results list.
- Loop over every feedback string.
- Remove surrounding spaces.
- Keep the text only when it is not empty.
- Return the finished list and print it.
Expected result: ["Great service", "Delivery was late"]
Optional challenge: Return each kept message in lowercase by calling .lower() after .strip().
Reveal solution
Starter code:
def prepare_feedback(items):
# Write your code here
pass
feedback = [" Great service ", "", " ", "Delivery was late "]
print(prepare_feedback(feedback))def prepare_feedback(items):
prepared = []
for item in items:
tidy = item.strip()
if tidy:
prepared.append(tidy)
return prepared
feedback = [" Great service ", "", " ", "Delivery was late "]
print(prepare_feedback(feedback))
# ['Great service', 'Delivery was late']For the optional challenge, change tidy = item.strip() to:
tidy = item.strip().lower()Key takeaways
- A variable gives a value a useful name.
- A list keeps several values in order.
- A loop repeats the same operation for each item.
- A function turns named steps into reusable input-to-output behaviour.
- Create fresh output lists so separate calls do not accidentally share data.
Flashcards
Flashcards
1 of 12Revision and interview questions
- Explain the path from raw_messages to the returned cleaned list.
- What would happen if return cleaned were indented inside the for loop?
- Why should an AI application clean and validate text before sending it to a model?
- In an interview, how would you explain the mutable-default list bug and its fix?
- Compare a comprehension with a generator and choose one for a large document stream.
- Explain how a decorator and context manager keep AI application code reliable.
- Describe a safe exception-and-logging policy for an API response parser.
One-page sketchnote summary
Print me · one-page revision sheet
Python foundations for AI applications
Main idea
Python connects the parts of an AI application. A dependable function receives messy input, applies small visible steps, and returns predictable output.
Core terms
- Variable — a name for a value
- List — ordered values in square brackets
- Loop — repeats work
- Function — reusable named instructions
- Return — sends a result back
- Type hint — expected data shape
- Generator — on-demand values
- Context manager — guaranteed cleanup
One picture
Code pattern
result = []
for item in items:
tidy = item.strip()
if tidy:
result.append(tidy)
return resultWatch out
- Do not return from the first loop pass
- Do not mutate the list you are visiting
- Do not use [] as a default parameter
- Do not use bare except
- Do not log secrets or personal data
Remember
- Name data clearly
- Move data through small steps
- Build a fresh result
- Check output before using a model
- Stream large work when possible
- Keep shared reliability behaviour small
Three quick questions
- What does .strip() remove?
- Why is return after the loop?
- How do you make a safe list default?
- Why is a generator single-use?
- What does with guarantee?
Lesson checklist
0 of 11 completeResources
- docsPython tutorial — an informal introductionThe official introduction to values, strings and lists used in this lesson.
- docsPython tutorial — defining functionsThe official reference for function parameters, return values and default arguments.
- docsPython tutorial — data structuresA useful next reference once lists and loops feel familiar.