Opinions · Python Philosophy
Python Philosophy
AI now writes much of the code. What still pays off is an intuitive picture of what code does, so that you can read it, check it, and know when it is wrong.
From 2022 to 2023, I created and taught a free online Python tutorial: 46 students in four cohorts, many of them with no programming background at all. Much of what I wrote for it was about details: which method does what, which syntax to use, where to look things up. A few years later, an AI assistant handles most of those details faster than I could look them up. But my advice on how to think about code has, if anything, become more useful, because working with AI means spending more time reading code than writing it. This page collects that advice, revised for the AI era. Practical notes for learners, and how the tutorial worked, are on Learning Python.
Write it, read it, or know it exists
In my study guide, I gave every topic one of three goals:
- Write: use it to write code that does what you need, as fluently as possible.
- Read: understand how it works, well enough to follow other people's code that uses it.
- Know it exists: know that Python can do this, and learn the details when you need them.
Back then, most of the course aimed at writing. Today, AI covers much of the writing and almost all of "know it exists": it remembers every string method and every keyword argument. Reading is the level that remains with the person, and it is what the rest of this page is about. Writing still matters, because reading code well comes from having written it, but I would now shift the balance toward reading.
Try it, unless you can predict it
Programming is a practical art. If you want to know whether something works, the best way is usually not to ask someone, but to try it yourself. I asked my students to run every example, and then to change it and see what happens.
There was one exception: if you see the input and already know the output, or if you can say what every line of a script does, you do not need to run it. Judging this takes honesty. As Confucius put it, 知之为知之,不知为不知: to know what you know, and to admit what you do not know. If you are not sure of the result, trying it yourself is the safer way.
The same test now applies to code an AI writes for you. If you can say what each line does, you have checked it. If you cannot, run it, print what is inside, and find out, before you build anything on top of it.
Inputs and outputs first
Before writing a program, be clear about what goes in and what comes out, both for the whole program and for each part of it.
- The whole program usually reads and writes files. How is the input organized, and which data does the program actually need? Is the output for a person or for another program? What does that reader need from it, and which format serves them best?
- Each function takes arguments and returns a value. What does it need to be told to do its job? What should it do, and what should it give back, for the code that calls it?
Unless a file is a final product for people to read, I write the code that writes it and the code that reads it at the same time.
This is also what a good request to an AI contains. If you cannot say what goes in and what should come out, the AI will guess, and the Zen of Python warns against exactly that: "In the face of ambiguity, refuse the temptation to guess."
Names are pronouns
Writing a program with a clear head means knowing, at every line, what each name refers to: its type and its content. If you do not know, find out with type and print, especially when you use code written by someone else. Code written by an AI is code written by someone else.
The picture I found most helpful is that names in Python work like pronouns. You do not declare them in advance; a name simply refers to whatever was last given to it. The same holds for functions: an argument is like a noun, and a parameter like a pronoun. When you write a function with a parameter arg, the function works on "the thing called arg"; when the program runs, it works on "whatever was handed over as arg."
b = a, both names refer to the same list, so b.append(4) changes what a refers to as well. Python Tutor shows this step by step and is great for seeing it for yourself. Made with AI assistance (Claude).Pronouns explain one of the most common surprises in Python. Here, b is not a copy of the list; it is a second pronoun for the same list:
>>> a = [1, 2, 3]
>>> b = a
>>> b.append(4)
>>> a
[1, 2, 3, 4]
So my rule for beginners was: unless you have a reason to, avoid giving one thing two names, or two things one name. Follow it, and you can even skip learning about variable scope.
Objects you can ask
In Python, everything a name refers to is an object: numbers, lists, dictionaries, even functions. Take a complex number z = 3 + 4j. Its real and imaginary parts, z.real and z.imag, are its attributes: data that belongs to it. Its complex conjugate, z.conjugate(), comes from a method: a function that belongs to it. The procedural way of thinking treats z as a lifeless thing that you drop into a machine, which returns its conjugate. The object-oriented way calls z over and asks it: what is your conjugate? Python offers both, as in sorted(items) and items.sort().
Most people who use Python never need to design their own classes, but almost everyone uses packages organized as classes, so this picture is worth having. And modular, object-oriented thinking is a powerful way of organizing ideas, even away from a computer.
Do it by hand first
When a task is new, I first imagine doing it by hand. In one exercise, students listed the derangements of four numbers, the orderings in which no number stays in its place, with pencil and paper: first all 24 orderings, then the 9 that qualify. Only then did they write the program, and then make it work for any number of elements.
Another example came from real work: counting how many times each user voted, from a file with one user ID per vote. Before the real data arrived, we simulated some, so the program could be written and tested in advance. Before writing any code, we decided how to store the result: a dictionary from user to count. The first working version is often slow. Imagining the job by hand suggests a better one: you would not scan the whole list once for every user, but go through it once, adding a tally mark for each vote. And then, almost always, someone has already written it:
# Slow: one pass through all the votes for each user
counts = {user: votes.count(user) for user in set(votes)}
# Tally marks: one pass, one mark per vote
counts = {}
for user in votes:
counts[user] = counts.get(user, 0) + 1
# Someone has already written it
from collections import Counter
counts = Counter(votes)
An AI will usually jump straight to the third version, and that is fine. Having walked through the first two is what lets you see why the third is right, and roughly how long it should take.
The shape of good code
- Functions should be independent of each other (if two functions are entangled, they probably belong together), easy to reuse elsewhere with few or no changes, and of moderate length: merge one that is too short into its caller, and split one that is too long. A program organized into functions reads like an essay with a thesis and supporting paragraphs.
- Comments are often neglected by beginners, but they matter. My test: when you ask someone for help with your code, you should not need to explain in other words what it does. Good names reduce the need for comments. For a complicated program, I sometimes write the comments first, as a scaffold, and fill in the code afterward.
- Repetition is a signal: three or more lines that look almost the same should usually become a loop. Write nested loops one level at a time.
- Plain over clever: recursion shows computational thinking beautifully, but unless it is truly needed, I prefer a loop in practice.
- Style: PEP 8 is a good guide, but the minimum is consistency within a project. When torn between two ways of writing something, run
import thisand read the Zen of Python.
These are also the qualities to ask of AI-written code, because they are what makes code possible to review. The comment test applies directly: now the "someone" you ask is often an AI, and the reviewer is you.
What is worth your time
I used to tell students that the time a programming task costs has three parts: learning what you need, writing and debugging the code, and running it. Running time can usually be ignored, since it is either short or something you can wait out while doing other work. Code that others have written is usually more systematic and more elegant than your own, but if learning it would take too long, it can be faster to write your own: code is written to get a job done, not to be admired.
AI has changed this calculation. Learning a library now costs very little, which tips the balance toward existing tools: when someone has already written what you need, an AI can show you how to use it in minutes. What it does not change is the rest of this page. Whoever wrote the tool, you still need to know what it does to your data.