Tutorials
Practical tutorials for the work we actually do. Pick one, read it in order, and apply it.
A closure remembers variables from the function that created it. Build separate counters with no globals, fix the UnboundLocalError with nonlocal, avoid the lambda-in-a-loop trap, and know when a class is clearer.
Don't guess which part is slow. Run cProfile, read cumtime and tottime the right way, fix the one line that matters, and measure again. In the example, the obvious suspect isn't the problem.
A for loop calls iter() once and next() until StopIteration. Write __iter__ and __next__ yourself, avoid the iterator that only works once, and know when a generator in __iter__ does the same job in fewer lines.
If a class mostly holds data, @dataclass writes __init__, a readable printout and equality for you. Use field(default_factory=list) for lists, __post_init__ for checks and frozen=True for values that must not change.
ModuleNotFoundError has a handful of causes: the wrong Python, an inactive virtual environment, the wrong folder, or a file that shadows a module. Check them in order instead of reinstalling.
Defaults make functions easier to call and keywords make calls easier to read. A list as a default value is created once and shared by every call. Here's why, and the one-line fix.
An if/elif chain stops at the first true condition. Put the widest one first and the others never run, with no error. Order your conditions, test the edges, and make else mean what you think.
Importing a file runs it. if __name__ == "__main__" keeps a file's script part from running when another file imports it. Use it with a main() function, and know the cases where you don't need it.
Python is probably installed. Your terminal just can't find it. Check the three possible causes in order, fix the right one on Windows, Mac or Linux, and confirm which Python you're really running.
/, //, % and round() each do exactly what they say, and that's not always what school math taught you. Negative numbers, halves and money are where the surprises are, and where Decimal comes in.
print() is for the output a person asked for. logging is for messages about the program: levels you can turn down, a file with times, and tracebacks for errors. Set it up once in main() and use a logger per module.
Start mypy with its defaults, fix what it finds, turn on check_untyped_defs, then make one module at a time strict. Every function you annotate checks every place that calls it, so start with the busiest ones.