I think due to lazy evaluation statements like:
if True and True (...):
# do something
if condition == True:
# do something
It's not much compilation per se (CPython does compile to bytecode), but the extremely dynamic nature of the language hampers "traditional" optimizations, which are guided by invariants which are difficult to check at compile time in Python, because the semantics of the language is such that most information about what the code is actually supposed to do is only available at runtime.
if condition == True: example is a good one: that comparison could be optimized only if the compiler could prove that
condition is always a boolean, which is a non-trivial task if it derives from anything but literals in the current scope - and if it could prove that, at runtime, nobody managed to overwrite
True with something else (possible only in Python 2 IIRC).
Mind you, some type inference is possible, and it's actually how code completion tools like Jedi work; however, Jedi can take some shortcuts and assume e.g. some kind of standard environment, while the interpreter has to cope with the most bizarre modifications to the globals that the language do allow.
So, in general CPython does not try very hard to optimize anything - actually, it doesn't seem to do any kind of "smart" code analysis, AFAICT they mostly try to build an efficient but "mostly vanilla" interpreter. So, expect to pay almost exactly for what you write, there's no optimizer to save sloppy code.
By the way, your
and example is not an optimization done by the interpreter, it's just how the semantic of the operator is defined (and it's quite crucial that it's specified that
or do short circuit evaluation and it's not an optional "optimization", since it could alter the observable behavior of the program if the right hand operand had side effects).
A better approach for a language that dynamic is instead that of a tracing JIT - the interpreter lets the code run for a while, and observes the invariants that seem to hold well (for example,
condition seems to always be a boolean,
True is always
True, some variable is always an integer, ...), and then uses this information to compile to machine code an optimized version of the code. This is let run as long as the invariants above do hold - but as soon as one of the premises used to compile the optimized version is broken, the execution goes back into interpreted mode, and tracing begins again to find out if a new optimized version can be built.
Thus, the typical case is optimized well, applying the usual optimization techniques, but whenever something bizarre (but allowed by the language) happens, the runtime can fallback to the regular "literal" bytecode interpretation.