The GIL doesn't really help Python code though, because the interpreter may switch threads between any two opcodes.
It only protects the state of the Python interpreter and that of C/Cython extension modules. Though even there, you can have unexpected thread switches, e.g. in Cython `self.obj = None` can result in a thread switch if the value previously stored in `self.obj` had a `__del__` method implemented in Python.
And AFAIK pretty much any Python object allocation can trigger the cycle collector which can trigger `__del__` on (completely unrelated) objects in reference cycles, so it's pretty much impossible to rely on the GIL to keep any non-trivial code block atomic.