Python 2 to Python 3 migration used the D-day approach which failed. We learnt from our mistake and we are introducing incompatible changes differently now. Document changes, provide a way to write code compatible with the old and the new way, tooling to ease the migration, design long term approach to reduce the need for incompatible changes.
In the Python 2 era, it was decided to migrate at a D-Day: convert all your code base to Python 3. It didn't go as well as expected. We learnt lessons from this mistake. Incompatible changes are now introduced differently in Python.
Today, changes start with a deprecation warning for at least two Python releases before removing old functions. We think about how to write a single code base working on the old and new Python versions. More and more often, instructions to migrate existing code are provided, or even automated tools. Changes breaking too many projects are reverted when there is not enough time to update enough projects. Code search helps detecting affected projects, notify them, and maybe also propose changes to prepare their code.
In the future, Python is working on a stable ABI to be able to build C extensions once and use them on many Python versions. The HPy project is an interesting candidate for this goal. More and more projects are being tested on the Python version currently under development (Python 3.12).