You do this as you go. When it becomes hard to read or hard to work on, it’s usually not difficult to figure out why. I usually come up with a solution right then and there, regardless of whether it’s Python or something else. It’s quite difficult to give you a good complete answer here, because the good info on this is usually in some book I forgot the title of, and books about this appear by the hundreds every year.
I think some good principles for a beginner are:
don’t repeat yourself (aka DRY): if you have the same piece of code all over the place, it probably should be a function.
separation of concerns: a function should only do one thing, and not have extra side-effects. A mild case of this would be a function which sets a value but also returns it. An extreme case would be a function which reads a value from a database to return it, but while doing so also writes to the database.
To prevent the worst offense right away, please don’t try to wrap everything in a class. Objects have state (aka fields, aka members, aka properties, aka instance variables, aka aka aka), which is an abstraction that helps to bundle related data together, but it also obfuscates what the data is; think “When was this value last set? What is it going to be when my function uses it?”. It creates indirection: the worst contender for readability. Abstractions always trade readability for manageability, and they can often be a really bad deal.
Some design patterns will even directly contradict general principles. E.g. the builder pattern low-key violates separation of concerns, by continuously returning the same object on which you called a function, e.g.:
A good strategy is to not try to improve anything unless you have a strong feeling that you should.
Good enough is good enough.