Programming for Artificial Intelligence
Python and Computational Thinking · 15 min
Computational thinking is the discipline of turning a fuzzy problem statement into a sequence of precise, mechanically executable steps before a single line of code is written; it rests on decomposition (breaking a big problem into smaller ones), pattern recognition, abstraction, and explicit algorithm design. Consider a concrete worked example: "compute the average of a list of sensor readings, ignoring any reading below zero." Decomposed, this becomes: create a running total and a count, examine each reading in turn, skip it if it is negative, otherwise add it to the total and increment the count, and finally divide the total by the count. Only after this decomposition is complete does it make sense to open an editor. The official Python tutorial frames Python itself as an interpreted, high-level, general-purpose language whose syntax was designed to make this translation from plan to code unusually direct, which is a large part of why it has become the default language for expressing algorithms in AI coursework and research.
Python's control-flow constructs map onto the decomposed plan almost line for line. The official documentation on control flow tools describes the `for` statement as iterating "over the items of any sequence... in the order that they appear," not merely over integer counters as in C-style loops, so a Python `for` loop can walk directly across a list of sensor readings rather than an index range. The sensor-average example becomes: `for reading in readings:` followed by an `if reading < 0: continue` to skip invalid values, then accumulation. The same documentation describes `range()` for when an explicit index is genuinely needed, and the optional `else` clause on loops, which runs only when the loop completes without hitting a `break` — a construct useful for search-style algorithms that need to distinguish "found" from "exhausted the list."
This tight correspondence between plan and syntax is also why Python dominates applied AI work despite not being the fastest language available. Its interpreted nature supports an interactive read-evaluate-print loop that lets a learner test a single expression's behavior in seconds rather than compiling a whole program, which shortens the feedback cycle central to computational thinking: hypothesize, test, revise. Combined with a very large standard and third-party library ecosystem, Python functions as a "glue language" that lets a developer express high-level algorithmic logic in readable syntax while numerically intensive inner loops are often implemented underneath in faster compiled code — a division of labor that later lessons in this module and in the data-structures module build directly on.
Implementing and Testing Algorithms · 15 min
Writing an algorithm is only half the job; knowing whether it is correct is the other half, and guessing from a handful of manual runs does not scale as code grows. Python's built-in `unittest` framework, modeled on JUnit, formalizes this by organizing verification around test fixtures (the setup and cleanup an individual test needs), test cases (individual checks written as methods on a class that subclasses `unittest.TestCase`), test suites (collections of test cases run together), and test runners that execute everything and report results. Consider the sensor-average function from the previous lesson: a test case would call it with a small, known list such as `[10, -5, 20]` and assert that the result equals `15.0`, using the `assertEqual(a, b)` method, which checks `a == b` and reports a clear failure message naming both values if the assertion does not hold.
Beyond the single happy-path check, disciplined testing means deliberately probing edge cases: an empty list, a list where every reading is negative, a list containing exactly one valid reading. The `unittest` documentation describes `setUp()` and `tearDown()` methods that run automatically before and after each test method to prepare and clean up shared state, and a `subTest()` context manager for reporting failures separately across a loop of parameterized inputs rather than stopping at the first one. It also documents automatic test discovery — running `python -m unittest discover` locates every file matching a `test*.py` pattern and executes the `TestCase` classes inside it — which is why the convention of keeping tests in separate `test_*.py` modules rather than mixed into the implementation file matters: the implementation can change shape during refactoring while the tests, which change far less often, keep verifying its external behavior.
In AI-adjacent programming specifically, tests earn their keep by catching a class of bug that is otherwise nearly invisible: a numerical computation that runs without raising any error but returns a subtly wrong number, such as an averaging function that silently includes an invalid reading because a comparison operator was flipped. A single manual run against sample data might never expose this, while a small suite of `assertEqual` checks against hand-computed expected values pins the function's contract down permanently, so that the next change to the code — or to a library dependency — either preserves that contract or fails loudly and immediately rather than corrupting downstream results.
Debugging and the Python AI Library Ecosystem · 15 min
When a test fails or a program produces an unexpected result, the systematic alternative to inserting scattered `print()` statements is Python's interactive debugger, `pdb`, which lets a developer set breakpoints, execute code one line at a time, and inspect the values of variables at the exact moment something goes wrong. Since Python 3.7, the simplest way to pause execution is to insert the built-in `breakpoint()` call at the suspect line; when the interpreter reaches it, it drops into an interactive `(Pdb)` prompt where the developer can type `p x` to print the current value of a variable named `x`, `l` to list the surrounding source lines, and `w` to print the call stack showing how execution arrived there. Consider the sensor-average function again: if it returns an unexpectedly low average, placing `breakpoint()` just inside the loop and repeatedly typing `p reading` and `p total` while stepping forward reveals exactly which reading was mishandled and why.
The debugger's stepping commands encode an important distinction. The `step` (`s`) command executes the current line and, if it calls another function, pauses at the first line inside that function; the `next` (`n`) command executes the current line but runs any function call to completion without stopping inside it, treating the call as a black box. Choosing `next` over a suspect helper function you already trust — versus `step` into a function you suspect is the actual source of the bug — is itself a small piece of computational thinking: it directs limited attention toward the part of the program most likely to be wrong. The `continue` (`c`) command resumes normal execution until the next breakpoint or the program's end, and `pdb` also supports post-mortem debugging, letting a developer inspect the state of a program at the moment it crashed rather than only while it is still running.
Once a learner can write, test, and debug a function like the sensor-average example, the same skills scale directly to the Python libraries that make up the applied AI ecosystem. Numerical array libraries, dataframe libraries for tabular data, and machine-learning estimator libraries are, mechanically, ordinary Python packages: they define functions and classes imported the same way, their outputs can be spot-checked with `assertEqual`-style tests against small known inputs, and unexpected results inside them are diagnosed with the very same `breakpoint()` and stepping workflow described above. Framing AI systems, in Russell and Norvig's terms, as programs that perceive their environment and act to achieve goals makes clear why this foundation matters: every rational-agent behavior described at that level of abstraction is, underneath, ordinary Python code that must be written, tested, and debugged with exactly this discipline before it can be trusted to act.
Write, Test, Debug, Refine
Systematic Python development moves in a loop: write code from a decomposed plan, test it against known inputs, debug any failure with pdb, then refine before repeating.
- Decompose a problem into precise steps before writing any code — computational thinking happens on paper first.
- A unit test that pins down expected output on a small known input catches numerical bugs that a single manual run would never reveal.
- pdb's next steps over a trusted function call while step dives inside it — choosing between them focuses debugging effort where the bug is most likely to be.
Recall Practice
Glossary
- Interpreter
- A program that executes Python source code directly, statement by statement, rather than compiling it entirely to machine code first.
- Control flow
- The statements — such as for, while, if, and break — that determine the order in which a program's instructions execute.
- Unit test
- A small, automated check, written as a TestCase method, that verifies one function or behavior returns an expected result for a given input.
- Breakpoint
- A marked point in code, set with breakpoint() in Python, where execution pauses and drops into the interactive pdb debugger.
- Traceback
- The report Python prints when an unhandled exception occurs, showing the chain of function calls that led to the error.
- Docstring
- A string literal placed as the first statement in a function, class, or module that documents what it does.
Hand-Trace a Debugging Session
Working from a supplied short Python function containing one deliberate bug and a printed transcript of pdb commands (breakpoint, next, print), learners hand-trace the value of each variable line by line and predict what each command would report before checking the provided answer key. This is a virtual, paper-based tracing exercise using only the supplied code and transcript — no code is actually executed.
Ready to test yourself?
5 questions on this module.