How to Migrate From Python 2 to Python 3

24 Jun 2024

As Python 2 reached its end of life on January 1, 2020, migrating to Python 3 has become essential for maintaining security, performance, and access to new features.

I had never worked with a code legacy before moving to the UK. The current project I am working on has 12000+ commits, and not so far in the past have we decided to migrate from Python 2 to Python 3.

I want to share all the problems and key pain points I’ve encountered to make your experience smoother. Let’s do it!

1. Syntax Changes

That was probably the easiest part to change with a search in IDE replacing all required statements. Let’s take a look at only one example

Print Function: In Python 3, print requires parentheses.

Python 2: print "Hello, World!"
Python 3: print("Hello, World!")

I will not even focus too much on this, as there is really nothing to tell you. You just look at the new syntax for Python 3, and after that, search for python2 statements and replace them with new python3 statements.

2. Unicode and Strings

This may be an unpleasant experience, but also should be added to your checklist during migration.

String Handling: In Python 3, all strings are Unicode by default, while Python 2 has ASCII strings and Unicode literals.

Python 2: u"Hello, World!"
Python 3: "Hello, World!"

Byte Strings: Explicit byte strings are required when handling binary data.

Python 3: b"binary data"

You need to be very patient and attentive in updating tests` assertions with logic throughout the codebase, as it may (or may not) break existing logic. Follow TDD!

3. Libraries and Dependencies

  • Standard Library Changes: Some modules have been renamed or reorganized.

  • Third-Party Libraries: While many libraries have migrated to Python 3, some might still lack support or have different APIs. Check compatibility and update or replace libraries as needed.

    That shouldn’t be a problem nowadays, as there is an existing tool that could simplify this process called caniusepython3.

    $ pip install caniusepython3 
    $ caniusepython3 -r requirements.txt

4. Function Annotations

Working with Python2, you will start appreciating having annotations across the project. Python 3 introduces function annotations for optional metadata, which are not present in Python 2.

Python 3: def greet(name: str) -> str:

The longest part will be to annotate the whole project. My fair recommendation would be to use a linter mypy and the following workflow:

  1. Add type annotations
  2. Run mypy
  3. Double-check
  4. Repeat steps 1-3
$ pip install mypy
$ mypy .

I’ve been missing this functionality the most, as it’s difficult to work without knowing the type of the object, and to be honest…


5. Compatibility Libraries

When migrating from Python 2 to Python 3, compatibility libraries like six make sure that code works in both environments. Consider six as a bridge between the two versions, making the transition smoother.

# Iterate over dictionaries in a way that works for both versions.
import six
my_dict = {'a': 1, 'b': 2, 'c': 3}
for key, value in six.iteritems(my_dict):
    print_(key, value)

Write tests for existing code before starting the migration to ensure that functionality remains intact.

The journey from Python 2 to Python 3 might seem daunting, but with a well-structured approach and the right tools, you can ensure a successful migration.

Hopefully, this article will be helpful if you are working on a big project and want to update it to a newer version of Python.

Thanks for your attention, and never use Python 2 in 2024! Best of luck!