Skip to content
Modernization

Python 2 to Python 3 migration: a practical checklist

Python 2 reached end of life on 1 January 2020, but many business systems still run on it. Here is a safe, step-by-step path to Python 3.

By Mechane Forge6 min read
Short answer

Python 2 stopped receiving updates on 1 January 2020, so code still running on it gets no security fixes. Migrate safely by adding tests first, upgrading dependencies, running both versions side by side with compatibility tools, fixing text and bytes handling, then switching over and removing the compatibility layer.

Python 2.7 was retired by the Python Software Foundation on 1 January 2020. Plenty of internal tools, scrapers, report generators and back-office scripts still run on it. They keep working, until a library, an operating system or an auditor forces the issue. Migrating on your own timetable is far cheaper than doing it in an emergency.

Why does Python 2 still matter?

  • No security patches: vulnerabilities found in the interpreter are not fixed.
  • Libraries have dropped support, so you cannot upgrade dependencies that contain fixes.
  • Modern operating systems and containers increasingly omit Python 2.
  • Hiring and maintaining is harder every year.

What is a safe migration order?

  1. 01Inventory every Python 2 script, service and cron job, including the ones nobody remembers.
  2. 02Add tests around current behaviour before touching code. Even a handful of end-to-end checks helps.
  3. 03Upgrade to the last Python 2.7 and pin dependencies, so changes happen one at a time.
  4. 04Run compatibility tooling such as pyupgrade, futurize or modernize, and static checks with mypy, to find incompatible code.
  5. 05Fix text and bytes handling, the biggest source of bugs, especially around files, networks and databases.
  6. 06Replace or upgrade dependencies that do not support Python 3.
  7. 07Run old and new side by side on real data and compare outputs before switching.
  8. 08Cut over, then remove the compatibility layer and update CI and deployment.

Which changes cause the most trouble?

AreaPython 2 behaviourWhat to check in Python 3
Stringsstr is bytes, unicode is textstr is text, bytes is separate; encode and decode at the edges
Division5 / 2 equals 25 / 2 equals 2.5; use // for integer division
printA statementA function
Dictionarieskeys() returns a listReturns a view; wrap in list() when needed
ImportsImplicit relative importsAbsolute imports only

Should you rewrite instead?

For small scripts, a rewrite can be faster. For systems with years of embedded business rules, migrate incrementally and keep the tests. If the source is lost or poorly understood, our legacy modernization service starts by documenting behaviour from the running system.

Have something that should run itself?

Tell us what is slowing your team down. We will come back within one working day with an honest view on whether it is worth building and what it would take.