Python Fundamentals¶
The bar: you can write correct, readable Python by hand — variables, control flow, functions, collections, files, errors, and classes — without reaching for an AI assistant to think for you.
Python is the language AI engineering runs on. Before you can build agents, wire up models, or parse data at scale, you need the base language in your hands: what a variable is, how a loop terminates, why a function returns what it returns. This module is the same deal as the ones before it — no AI tools. You type it until it's muscle memory.
The point isn't to memorize syntax. It's to reach a level where you read a stack trace and know what broke, and where you can express an idea in code without stopping to look up how a for loop works. That fluency is what separates someone who ships from someone who copy-pastes and prays.
5.1 Variables and Data Types¶
Objective: understand how Python represents and stores data.
Python is dynamically typed: a variable is just a name pointing at a value, and it can point at anything. That freedom is convenient and it bites people. type() tells you what you're actually holding when a bug says otherwise. The primitives you'll live in are int, float, str, bool, and None — and None is not zero, not empty string, it's the explicit absence of a value.
Conversion is explicit in Python. int("5") gives you a number; adding "5" + 5 is a TypeError, not silent coercion. Get comfortable converting at the boundaries where data enters your program. Strings are their own toolkit — f-strings (f"{name} shipped {count} PRs") are the modern default for building text.
The gotcha that trips self-taught engineers: mutable vs immutable. Strings, ints, and tuples can't be changed in place; lists and dicts can. Passing a list into a function and mutating it changes the caller's copy. Know which side of that line each type sits on.
Drill:
- Inspect values with
type()and convert withint(),float(),str(). - Build strings with f-strings and common
strmethods (.upper(),.strip()). - Predict which of
int,float,str,bool,None,list,tupleare mutable.
5.2 Operators and Expressions¶
Objective: manipulate data with Python's operators.
Arithmetic mostly reads like math, with two catches: / always gives a float, // floors to an int, and % is the remainder you'll use constantly for cycling and even/odd checks. ** is exponent. Comparison operators return bool and chain naturally.
Logical operators are and, or, not — words, not symbols. Membership (in, not in) is the clean way to ask "is this in that collection." The one that confuses people is identity: is checks whether two names point at the same object, == checks whether values are equal. Use is only for None (x is None), never for comparing numbers or strings.
When an expression gets dense, operator precedence decides evaluation order. Don't memorize the whole table — reach for parentheses to make intent obvious.
Drill:
- Work every arithmetic operator:
+ - * / // % **. - Combine
and,or,notwith comparisons in one expression. - Contrast
isvs==on identical-looking values.
5.3 Control Flow¶
Objective: control the order your program executes in.
if / elif / else is your branching. Python leans on truthiness: empty collections, 0, "", and None are falsy, so if items: is idiomatic for "if the list has anything." Writing if len(items) > 0: works but marks you as new.
for loops iterate over collections directly — you rarely index by number. range() generates numbers when you genuinely need a counter. while runs until a condition flips; forget to change the condition and you've got an infinite loop. break exits, continue skips to the next pass, pass is a do-nothing placeholder.
List comprehensions are Python's signature move: [x*2 for x in nums if x > 0] builds a list in one readable line. Learn them, but don't nest three deep — at that point a plain loop is clearer.
Drill:
- Branch with
if/elif/elseusing truthy/falsy checks. - Loop with
for,range(), andwhile; control withbreak,continue,pass. - Rewrite a filtering loop as a list comprehension, including a nested loop.
5.4 Functions¶
Objective: write reusable functions.
def defines a function. Parameters come in flavors: positional, keyword, and default (def greet(name, greeting="Hello")). *args collects extra positional arguments into a tuple; **kwargs collects extra keyword arguments into a dict. You'll see both constantly in library code.
Functions return with return; hit the end without one and you get None. You can return multiple values as a tuple and unpack them at the call site. Scope matters: names assigned inside a function are local by default, global and nonlocal reach outward — use them sparingly, since reaching out is usually a design smell.
lambda makes tiny anonymous functions for one-off use, like a sort key. And every function you write deserves a docstring — the triple-quoted line under def that says what it does. That's documentation your future self and teammates actually read.
Drill:
- Define functions with positional, keyword, and default parameters.
- Capture extras with
*argsand**kwargs; return and unpack multiple values. - Write a
lambdasort key and add a docstring to every function.
5.5 Lists and Tuples¶
Objective: work with ordered collections.
Lists are your workhorse ordered collection — created with [], indexed from 0, and indexed from the end with negatives (items[-1]). Slicing (items[start:stop:step]) pulls sub-ranges and is worth real practice; items[::-1] reverses a list, and off-by-one on stop is the classic bug.
Mutating methods matter: append adds one item, extend merges another collection, insert places at an index, remove deletes by value, pop removes and returns. A common trap: sort() sorts in place and returns None, while sorted() returns a new list and leaves the original alone. Assigning x = mylist.sort() gives you None — a bug people hit repeatedly.
Tuples are immutable lists — (1, 2, 3) — used for fixed groupings you don't want changed. Unpacking (x, y = point) works on both and is everywhere in idiomatic Python.
Drill:
- Index and slice with
start:stop:step, including negative indices. - Use
append,extend,insert,remove,pop. - Contrast
sort()vssorted(); unpack a tuple.
5.6 Dictionaries and Sets¶
Objective: use key-value and unique collections.
Dictionaries map keys to values — {"name": "Ada"} — and are how you model structured records. Access with d["key"], but that raises KeyError on a missing key; d.get("key") returns None (or a default you pass) instead, which is safer for uncertain data. Add and modify by assignment, delete with del. The keys(), values(), and items() methods drive iteration, and for k, v in d.items(): is the standard way through a dict.
Dictionary comprehensions build dicts in a line: {k: v*2 for k, v in d.items()}. Sets are unordered collections of unique values — created with {...} or set(), grown with .add(), and pruned with .remove() — great for deduplication and fast membership tests. Set operations (union, intersection, difference) answer "what's in both" or "what's only here" without loops.
Rule of thumb: use a dict when you need to look something up by key, a set when you only care whether something is present.
Drill:
- Create dicts; access safely with
.get(); iterate with.items(). - Add, modify, and
delkeys; build a dictionary comprehension. - Build a set,
.add()and.remove()items, then rununion,intersection,difference.
5.7 String Processing¶
Objective: manipulate text data effectively.
Text handling is most of real-world programming. split breaks a string into a list, join stitches a list back into a string, strip trims whitespace, and replace swaps substrings. For searching, find returns an index or -1, index raises if it's missing, and count tallies occurrences.
Formatting is f-strings first, with .format() as the older fallback you'll still meet in existing code. When patterns get complex — validating an email, pulling every date out of a log — regular expressions via the re module are the tool, though they're easy to over-reach with; a plain in check often beats a regex. Multi-line strings use triple quotes.
Encoding is where beginners get burned: strings are text, bytes are raw data, and .encode() / .decode() convert between them. When a file or API hands you bytes and you expected str, this is why.
Drill:
- Transform text with
split,join,strip,replace. - Search with
find,index,count; format with f-strings andformat(). - Write a basic regex with
re; encode and decode a string.
5.8 File I/O¶
Objective: read from and write to files.
Open files with open(), but always inside a with block — with open("data.txt") as f: closes the file automatically even if your code throws. Forgetting to close files is a resource leak that self-taught engineers ship all the time.
Read with read() (whole file), readline() (one line), or readlines() (list of lines); iterating the file object line by line is the memory-friendly default for large files. Write with write() and writelines(). File modes decide behavior: r reads, w overwrites, a appends, r+ reads and writes. Confusing w for a and wiping a file is a rite of passage — do it once in practice, not in production.
Where a file lives matters as much as how you read it. Build paths deliberately rather than gluing strings with slashes — pathlib.Path (or os.path) joins directories and filenames in a way that works across operating systems, so your script doesn't break the moment it runs somewhere new.
Most data you touch will be JSON. json.load() reads it from a file into Python dicts and lists; json.dump() writes Python data back out. That pairing is your bridge between disk and program.
Drill:
- Open files with
with open(...); read viaread,readline,readlines. - Write with
write/writelinesacross modesr,w,a,r+. - Round-trip data with
json.load()andjson.dump().
5.9 Error Handling¶
Objective: handle errors gracefully.
Real programs fail — files vanish, APIs time out, users type garbage. try / except lets you catch that instead of crashing, and finally runs cleanup either way. The discipline: catch specific exceptions (except ValueError:), not a bare except: that swallows everything including the bugs you needed to see.
raise throws an exception on purpose when your code hits a state it can't honor, and you can define custom exception classes for your domain's failures. Python's exceptions form a hierarchy — ValueError and TypeError are both Exceptions — so you can catch broadly or narrowly on purpose.
When something misbehaves, print() debugging is fine to start, but learn breakpoint() — it drops you into an interactive debugger at that line so you can inspect state live instead of guessing.
Drill:
- Wrap risky code in
try/except/finally, catching specific exceptions. raisea built-in and define a custom exception.- Debug with
print()andbreakpoint().
5.10 Object-Oriented Basics¶
Objective: understand classes and objects.
A class is a blueprint; an object is an instance built from it. __init__ is the constructor that runs when you create one, setting up instance variables. self is the reference to the specific instance — it's the first parameter of every method, and forgetting it is the most common early mistake.
Instance variables belong to one object; class variables are shared across all instances of the class — a distinction that surprises people when a shared list mutates everywhere at once. Inheritance lets one class extend another, reusing and specializing behavior, which is how most frameworks are structured.
Two dunder methods earn their keep early: __str__ gives a human-readable string for print(), and __repr__ gives an unambiguous one for debugging. Define them and your objects stop printing as cryptic memory addresses.
Drill:
- Define a class with
__init__, instance variables, and methods usingself. - Add a class variable and a subclass via inheritance.
- Implement
__str__and__repr__.
5.11 Hands-On Lab: Python Projects¶
Objective: build small projects from scratch, entirely by hand, no AI tools.
What you ship
Four working programs, each written by hand from the fundamentals in this module:
- A text-based game that loops on user input and branches on control flow.
- A file organizer script that reads a directory and moves files by type — exercising File I/O and paths.
- A data parser that ingests CSV or JSON and turns it into Python objects — exercising collections and
json. - A simple API consumer that fetches data and processes the response — exercising dicts, error handling, and strings.
Every line typed by you. The goal is fluency you can prove, not code you can generate.
On the VWC codebase
The Vets Who Code app is a TypeScript / Next.js codebase, not Python — so you won't ship this module's code into it directly. But the reasoning transfers straight across. Dicts and JSON here become the typed objects you pass around in src/lib/; try/except becomes try/catch; reading a file to parse JSON becomes the shape of a Prisma query or a src/pages/api/ handler returning structured data. Learn the fundamentals language-agnostically and the syntax swap is the easy part.