Once hello.py works, the next things worth learning aren't language features — they're habits. Three of them here. All boring, and all the difference between code you can still read next month and code you have to decode.
print("2026", "03", "13", sep="-") # 2026-03-13
print("Downloading", end="... ")
print("done")
sep is what goes between the values (default: a space), and end is what finishes the line (default: a newline). That second pair of lines prints Downloading... done all on one line. I use both constantly for quick output — but no more than this. The day a print call starts looking complicated is the day it should be an f-string, like f"{len(files)} files processed". Between them, sep also covers most of what beginners go looking for str.join for.
A comment is anything from a # to the end of the line. Python ignores it completely; it's written for humans. The test I try to apply: am I explaining what the line does, or why it does that?
What-comments are almost always waste:
i = i + 1 # add one to i
The code already says what. The comments that earn their pixels explain the why — especially when the code looks wrong on purpose:
i = i + 1 # row 0 is the header, data starts at row 1
Six months from now, why is the question you'll be asking. Comments that record a decision — "30 after the timeout problems, don't raise it back" — are why-comments in disguise, and they age especially well. I think of every comment as a note to whoever has to fix this file late some evening — and statistically, that's me.
A docstring is a string you put as the very first thing inside a function. Unlike comments, Python keeps it and shows it back to you:
def add_workdays(start, days):
"""Return the date `days` workdays after `start` (weekends don't count)."""
...
Call help(add_workdays) and the docstring is what prints — the same text also shows up in the editor's tooltip when you hover the function name. One good line is usually enough.
When something misbehaves, print the suspects as the program passes them:
for n in names:
print("checking", n)
It isn't sophisticated, but it's honest: you see exactly what the program saw, not what you assume it saw. A print placed just before the line that crashes usually points straight at the surprise value. When the prints start outnumbering the code, that's the moment to look up a real debugger — not before.
None of these three needs to be learned "properly" before moving on. Use sep once, write one comment that explains a deliberately odd line, put a docstring on your next function. Habits are just small things repeated.