Python 2 reached its end of life in 2020. If you still have Python 2 code somewhere, this page explains why it has to move, how projects did the migration, and what tools you can use today to port the last bits of legacy code.
Why should you migrate?
Because Python 2 is dead. Official support ended on January 1, 2020, and the final release, Python 2.7.18, came out in April 2020. There have been no bug fixes or security updates since, and there won’t be any.
The packages you depend on moved on as well. Practically every maintained package on PyPI dropped Python 2 years ago, and so did pip itself. Python 3.0 was released on December 3, 2008, so the community had more than a decade to switch. If you run Python 2 code today, you’re running software without security fixes on top of libraries that are just as old.
Besides security and support, Python 3 is simply a better language. You can read more about that on our page about Python 3’s advantages.
How projects migrated to Python 3
Most projects did two things: they upgraded their own code, and they upgraded their dependencies to versions that supported Python 3. The usual tools were:
2to3, a program that came with Python. It read Python 2 code and rewrote it into Python 3 code, for example turningprint "Hello"intoprint("Hello")andraw_input()intoinput().sixandfuture, two compatibility libraries that let a single codebase run on both Python 2 and 3 during the transition. Large projects often used them to migrate step by step.
That era is over. 2to3 was deprecated in Python 3.11 and removed in Python 3.13, so it’s no longer part of a current Python. The compatibility libraries aren’t needed anymore either, because there’s no reason left to support Python 2.
Porting legacy code today
If you have to port some old code now, you have these options:
- Port by hand. For a small script, this is often the quickest route. Fix the
printstatements, replaceraw_input()withinput(), check for integer division (5 / 2is2.5in Python 3), and watch out for the difference between text and bytes. - Run 2to3 from an older Python. Python 3.12 still includes it and is still supported. With uv, you don’t even need to install 3.12 yourself:
uv run --no-project --python 3.12 python -m lib2to3 -w yourscript.py. The-woption writes the changes to the file and keeps a.bakcopy of the original. - Clean up with pyupgrade or Ruff. Once the code runs on Python 3, pyupgrade and the UP rules of Ruff remove the Python 2 leftovers:
u""string prefixes,from __future__imports,class Foo(object),super(Foo, self)and old-style string formatting. They need code that’s already valid Python 3, though. They can’t convert aprintstatement.
Here’s what Ruff does to a typical piece of code that was written to run on both versions:
# -*- coding: utf-8 -*-
from __future__ import print_function
class Greeter(object):
def __init__(self, name):
super(Greeter, self).__init__()
self.name = name
def greet(self):
print(u"Hello, {0}!".format(self.name))
Greeter(u"Erik").greet()Code language: Python (python)
Running uvx ruff check --select UP --fix legacy.py turns it into this:
class Greeter:
def __init__(self, name):
super().__init__()
self.name = name
def greet(self):
print(f"Hello, {self.name}!")
Greeter("Erik").greet()Code language: Python (python)
For the dependencies, look up a current version of each package and expect some API changes along the way. Once your code runs on Python 3, you can also add a check for a minimum required Python version, so nobody accidentally runs it with an old interpreter.
Can you have Python 2 and 3 at the same time?
On a modern system, python and python3 both mean Python 3. Current Linux distributions no longer install Python 2, Homebrew dropped it, and the Python install manager on Windows doesn’t offer it. Not sure which version you’re running? Here’s how to check your Python version.
If you really need to run legacy Python 2 code, for example to compare its output with your ported version, keep it isolated from the rest of your system:
- Use a container. The official
python:2.7image on Docker Hub still exists, although it hasn’t been updated since 2020:docker run --rm -it -v "$PWD":/app -w /app python:2.7 python yourscript.py - On macOS or Linux, pyenv can still build Python 2.7.18 with
pyenv install 2.7.18. uv can’t help you here, because it only installs Python 3.
Either way, treat it as a temporary tool for the migration and don’t expose that code to the internet.