---
# === IDENTITY ===
id: software/migrations/python2-to-python3/2026
canonical_question: "How do I migrate from Python 2 to Python 3?"
aliases:
  - "Python 2 to Python 3 migration guide"
  - "convert Python 2 code to Python 3"
  - "port Python 2 to Python 3"
  - "upgrade Python 2 to Python 3"
  - "Python 2 to 3 porting guide"
  - "Python 2.7 to Python 3 migration"
  - "modernize Python 2 codebase to Python 3"
entity_type: software_reference
domain: software > migrations > python2_to_python3
region: global
jurisdiction: global
temporal_scope: 2008-2026

# === VERIFICATION ===
last_verified: 2026-05-17
confidence: 0.93
version: 2.1
first_published: 2026-02-17

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "Python 3.14 (2025-10) — from __future__ import annotations deprecated (PEP 649 native deferred evaluation)"
  next_review: 2026-11-13
  change_sensitivity: low

# === CONSTRAINTS ===
constraints:
  - "Python 2.7 is the only supported migration source — Python 2.6 and earlier must first upgrade to 2.7"
  - "2to3 and lib2to3 were removed in Python 3.13 — use futurize (python-future) or LibCST instead"
  - "Never mix str and bytes at I/O boundaries — Python 3 raises TypeError on implicit conversion"
  - "from __future__ import annotations is deprecated in Python 3.14 — use PEP 649 deferred evaluation instead"
  - "Dictionary ordering is only guaranteed from Python 3.7+ — do not rely on insertion order if targeting 3.6"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Codebase is already Python 3 only and needs upgrading between Python 3 versions"
    use_instead: "Consult Python release What's New docs for version-specific changes"
  - condition: "Code is fewer than 100 lines and can be trivially rewritten"
    use_instead: "Rewrite from scratch in Python 3 — no migration tooling needed"
  - condition: "Legacy system is being decommissioned within 6 months"
    use_instead: "Keep Python 2 isolated from network, run out the clock"

# === AGENT HINTS ===
inputs_needed:
  - key: codebase_size
    question: "How large is the Python 2 codebase?"
    type: choice
    options: ["Small (<10K LOC)", "Medium (10K-100K LOC)", "Large (>100K LOC)"]
  - key: dual_support
    question: "Do you need to support both Python 2 and Python 3 simultaneously during migration?"
    type: choice
    options: ["Yes — incremental migration", "No — one-shot conversion"]
  - key: binary_data
    question: "Does the codebase heavily handle binary data (network, files, crypto)?"
    type: choice
    options: ["Yes — significant bytes/str boundary work", "No — mostly text processing"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/migrations/python2-to-python3/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-05-17)"

# === RELATED UNITS ===
related_kos:
  related_to:
    - id: "software/migrations/jquery-to-react/2026"
      label: "jQuery to React Migration"
    - id: "software/migrations/javascript-to-typescript/2026"
      label: "JavaScript to TypeScript Migration"
    - id: "software/migrations/webpack-to-vite/2026"
      label: "Webpack to Vite Migration"
  alternative_to:
    - id: "software/migrations/python2-rewrite/2026"
      label: "Full Python 3 Rewrite (no incremental migration)"
  often_confused_with:
    - id: "software/migrations/python3-version-upgrade/2026"
      label: "Upgrading Between Python 3 Versions (3.x to 3.y)"

# === SOURCES (8 authoritative sources) ===
# Types: official_docs, technical_blog, rfc_spec, academic_paper, community_resource, industry_report
# Reliability: high, moderate_high, moderate, moderate_low, low, authoritative
sources:
  - id: src1
    title: "How to port Python 2 Code to Python 3"
    author: Python Software Foundation
    url: https://docs.python.org/3/howto/pyporting.html
    type: official_docs
    published: 2024-10-07
    reliability: authoritative
  - id: src2
    title: "2to3 - Automated Python 2 to 3 code translation (archived)"
    author: Python Software Foundation
    url: https://docs.python.org/3.12/library/2to3.html
    type: official_docs
    published: 2024-04-09
    reliability: authoritative
  - id: src3
    title: "Incrementally migrating over one million lines of code from Python 2 to Python 3"
    author: Dropbox Engineering
    url: https://dropbox.tech/application/incrementally-migrating-over-one-million-lines-of-code-from-python-2-to-python-3
    type: technical_blog
    published: 2019-02-05
    reliability: high
  - id: src4
    title: "Python 2.7 to 3.X Migration Guide: How to Port from Python 2 to Python 3"
    author: STX Next
    url: https://www.stxnext.com/blog/python-3-migration-guide
    type: technical_blog
    published: 2023-01-15
    reliability: high
  - id: src5
    title: "A Comprehensive Guide to Migrating from Python 2 to Python 3"
    author: Scout APM
    url: https://www.scoutapm.com/blog/python-2-to-python-3
    type: technical_blog
    published: 2022-06-10
    reliability: moderate_high
  - id: src6
    title: "Python 2 to Python 3: The Ultimate Migration Guide"
    author: Django Stars
    url: https://djangostars.com/blog/python-2-to-python-3-migration/
    type: technical_blog
    published: 2023-03-20
    reliability: moderate_high
  - id: src7
    title: "Overview: Easy, clean, reliable Python 2/3 compatibility"
    author: Python-Future Project
    url: https://python-future.readthedocs.io/en/latest/overview.html
    type: community_resource
    published: 2023-01-01
    reliability: high
  - id: src8
    title: "LibCST — A concrete syntax tree parser for Python"
    author: Instagram / Meta
    url: https://libcst.readthedocs.io/en/latest/
    type: community_resource
    published: 2025-01-15
    reliability: high
---

# How to Migrate from Python 2 to Python 3

## TL;DR

- **Bottom line**: Use automated tools (`futurize` or `python-modernize`) to handle syntax changes, then manually fix unicode/bytes boundaries and test with `python3 -bb` to catch type-mixing bugs. [src1, src2]
- **Key tool/command**: `futurize --stage1 --stage2 -w mypackage/` (converts Python 2 code to Python 2/3 compatible code with `future` as a runtime dependency)
- **Watch out for**: String/bytes conflation -- Python 3 strictly separates `str` (text) from `bytes` (binary), and code that mixes them will raise `TypeError` at runtime. [src1, src3]
- **Works with**: Python 2.7 (minimum source) to Python 3.8+ (target). Python 2 reached EOL January 2020; 2to3 removed in Python 3.13 (2024). [src1, src2]

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- **Python 2.7 required as baseline**: All migration tools require Python 2.7 as the minimum source version. Python 2.6 and earlier must first upgrade to 2.7 before any migration can begin. [src1]
- **2to3/lib2to3 removed in Python 3.13**: The `2to3` tool and `lib2to3` module were deprecated in Python 3.11 and removed entirely in Python 3.13 (October 2024). Use `futurize` from `python-future`, `python-modernize`, or `LibCST` instead. [src2, src8]
- **Never mix str and bytes implicitly**: Python 3 raises `TypeError` when concatenating or comparing `str` and `bytes`. Every I/O boundary (file, network, database) must explicitly `.encode()` or `.decode()`. [src1, src3]
- **`from __future__ import annotations` deprecated in Python 3.14**: PEP 649 (deferred evaluation of annotations) is now the standard mechanism. While the directive still works, plan for eventual removal. [src1]
- **Dictionary insertion order guaranteed only from Python 3.7+**: Do not rely on `dict` ordering if targeting Python 3.6 or earlier. Use `collections.OrderedDict` for guaranteed ordering on older targets. [src6]

## Quick Reference

| Python 2 Pattern | Python 3 Equivalent | Example |
|---|---|---|
| `print "hello"` | `print("hello")` | `from __future__ import print_function` enables Py2 compat [src2] |
| `except Exception, e:` | `except Exception as e:` | 2to3 `except` fixer handles this automatically [src2] |
| `dict.iteritems()` | `dict.items()` (returns view) | `for k, v in d.items():` works in both with `future` [src1] |
| `dict.has_key(key)` | `key in dict` | 2to3 `has_key` fixer handles this [src2] |
| `unicode("text")` | `str("text")` | In Py3, all strings are Unicode by default [src5] |
| `raw_input()` | `input()` | Py2 `input()` did `eval()` -- use `raw_input()` equivalent [src2] |
| `xrange(10)` | `range(10)` | Py3 `range` returns iterator like Py2 `xrange` [src2] |
| `5 / 2 == 2` (floor) | `5 / 2 == 2.5` (true div) | `from __future__ import division` or use `//` [src1] |
| `raise ValueError, "msg"` | `raise ValueError("msg")` | 2to3 `raise` fixer handles this [src2] |
| `long(42)` | `int(42)` | `long` type merged into `int` in Py3 [src5] |
| `map(fn, lst)` returns list | `list(map(fn, lst))` or comprehension | Py3 `map` returns iterator [src2] |
| `filter(fn, lst)` returns list | `list(filter(fn, lst))` or comprehension | Py3 `filter` returns iterator [src2] |
| `from StringIO import StringIO` | `from io import StringIO` | Standard library reorganized [src2] |
| `__metaclass__ = Meta` | `class X(metaclass=Meta):` | 2to3 `metaclass` fixer handles this [src2] |
| `b'data'[0] == b'd'` | `b'data'[0] == 100` (int) | Py3 bytes indexing returns int, not bytes [src1] |

## Decision Tree

```
START
├── Is the codebase still running Python 2.6 or older?
│   ├── YES → First upgrade to Python 2.7 (required baseline for all migration tools) [src1]
│   └── NO ↓
├── Do you need to support both Python 2 AND 3 simultaneously?
│   ├── YES → Use futurize or python-modernize + six for dual-compatible code [src1, src7]
│   └── NO ↓
├── Is the codebase <10,000 lines with good test coverage?
│   ├── YES → Run futurize for one-shot conversion, fix remaining issues manually [src7]
│   └── NO ↓
├── Is the codebase >100,000 lines or mission-critical?
│   ├── YES → Incremental migration: futurize stage1 first, then stage2, test each module [src3]
│   └── NO ↓
├── Does the code heavily handle binary data (network, files, crypto)?
│   ├── YES → Focus on unicode/bytes boundaries first; use type annotations + mypy [src3]
│   └── NO ↓
├── Are you targeting Python 3.13+ (where 2to3 is removed)?
│   ├── YES → Use futurize or LibCST — do NOT depend on 2to3 [src2, src8]
│   └── NO ↓
└── DEFAULT → futurize --stage1 --stage2 -w, then fix test failures one by one [src1, src7]
```

## Step-by-Step Guide

### 1. Ensure Python 2.7 baseline and add test coverage

All migration tools require Python 2.7 as the minimum source version. Before touching any code, ensure your test suite covers at least 80% of the codebase. [src1]

```bash
# Install coverage tool
pip install coverage

# Run tests with coverage
coverage run -m pytest tests/
coverage report --show-missing

# Target: 80%+ coverage before migration
```

**Verify**: `coverage report` shows >= 80% coverage on critical modules.

### 2. Add __future__ imports to prevent regressions

Add forward-compatible imports to every Python file. This makes Python 2 behave more like Python 3, catching issues early. [src1]

```python
# Add to the top of EVERY .py file
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals  # optional, see Caveats
```

**Verify**: `python2 -3 -Werror your_script.py` -- runs under Python 2 but raises errors for Python 3 incompatibilities.

### 3. Run futurize stage 1 (safe, mechanical fixes)

Stage 1 applies only safe, non-controversial transformations: print function, except syntax, dict methods, etc. [src7]

```bash
# Install python-future
pip install future

# Run stage 1 (safe fixes only)
futurize --stage1 -w mypackage/

# Review changes
git diff
```

**Verify**: `python2 -m pytest tests/` -- all tests still pass under Python 2 after stage 1 changes.

### 4. Run futurize stage 2 (Python 3 idioms)

Stage 2 adds `future` library imports to backport Python 3 types and builtins to Python 2. [src7]

```bash
# Run stage 2
futurize --stage2 -w mypackage/

# Review and test
git diff
python2 -m pytest tests/
```

**Verify**: `python2 -m pytest tests/` and `python3 -m pytest tests/` both pass.

### 5. Fix unicode/bytes boundaries manually

Automated tools cannot determine developer intent for string types. Audit every I/O boundary: file reads, network sockets, database queries, API responses. [src1, src3]

```python
# BEFORE: ambiguous in Python 2
data = open("config.txt").read()  # str (bytes) in Py2, str (unicode) in Py3

# AFTER: explicit encoding
with open("config.txt", "r", encoding="utf-8") as f:
    text = f.read()  # Always str (unicode)

# Binary files must use 'rb'
with open("image.png", "rb") as f:
    raw_bytes = f.read()  # Always bytes
```

**Verify**: `python3 -bb -m pytest tests/` -- the `-bb` flag raises `BytesWarning` as errors when bytes and str are compared or concatenated.

### 6. Update dependencies and standard library imports

Check that all third-party packages support Python 3. Replace renamed standard library modules. [src1, src4]

```bash
# Check dependency compatibility
pip install caniusepython3
caniusepython3 --requirements requirements.txt

# Common import changes (2to3 handles most of these):
# Python 2                    Python 3
# import ConfigParser    →    import configparser
# import Queue           →    import queue
# from StringIO import StringIO → from io import StringIO
# import urllib2          →    import urllib.request
# import urlparse         →    import urllib.parse
# import cPickle          →    import pickle
```

**Verify**: `python3 -c "import mypackage"` succeeds without `ImportError`.

### 7. Run the full test suite under Python 3

Use tox or CI to test across multiple Python versions. Fix remaining failures one by one. [src1, src3]

```bash
# Install tox
pip install tox

# tox.ini
# [tox]
# envlist = py38, py39, py310, py311, py312, py313
# [testenv]
# deps = pytest
# commands = pytest tests/

tox
```

**Verify**: `tox` reports all environments passing. Zero test failures across all target Python versions.

### 8. Remove Python 2 compatibility code and drop support

Once all tests pass on Python 3 and you have confirmed production stability, clean up compatibility shims. [src3, src4]

```bash
# Remove __future__ imports (no longer needed)
# Remove six/future imports
# Remove version-checking code (sys.version_info conditionals)
# Update setup.py classifiers:
#   'Programming Language :: Python :: 3 :: Only'

# Uninstall compatibility packages
pip uninstall six future

# Final check: no Python 2 references remain
grep -rn "python_requires.*2" setup.py setup.cfg pyproject.toml
```

**Verify**: `grep -rn "__future__\|import six\|from future" mypackage/` returns zero results (or only intentional uses).

## Code Examples

### Python: Fixing print, division, and string handling

```python
# Input:  Python 2 code with common patterns
# Output: Python 3 compatible code

# --- PYTHON 2 (BEFORE) ---
# print "Processing %d items" % count
# result = total / count  # floor division
# name = u"Muller"
# data = open("file.txt").read()
# for key, val in config.iteritems():
#     print key, val

# --- PYTHON 3 (AFTER) ---
print("Processing {} items".format(count))       # print is a function [src2]
result = total / count       # true division (returns float) [src1]
result_int = total // count  # floor division (explicit)
name = "Muller"             # all strings are Unicode [src5]
with open("file.txt", "r", encoding="utf-8") as f:
    data = f.read()          # explicit encoding [src1]
for key, val in config.items():  # .items() returns view [src2]
    print(key, val)
```

### Python: Migrating exception handling and class definitions

```python
# Input:  Python 2 exception and class patterns
# Output: Python 3 equivalents

# --- PYTHON 2 (BEFORE) ---
# class OldStyle:
#     __metaclass__ = ABCMeta
#     def method(self):
#         try:
#             risky()
#         except IOError, e:
#             raise RuntimeError, "Failed: " + str(e)

# --- PYTHON 3 (AFTER) ---
from abc import ABCMeta

class NewStyle(metaclass=ABCMeta):  # metaclass as keyword arg [src2]
    def method(self):
        try:
            risky()
        except IOError as e:  # 'as' syntax required [src2]
            raise RuntimeError("Failed: " + str(e)) from e  # chained exceptions [src5]
            # Note: exception variable 'e' is deleted after except block in Py3
```

### Bash: Automated migration with futurize

```bash
# Input:  A Python 2 project directory
# Output: Python 2/3 compatible codebase

# Step 1: Install tools
pip install future pylint tox coverage

# Step 2: Stage 1 conversion (safe mechanical fixes)
futurize --stage1 -w src/

# Step 3: Run tests to verify no regressions
python2 -m pytest tests/ -v

# Step 4: Stage 2 conversion (Python 3 idioms with future imports)
futurize --stage2 -w src/

# Step 5: Test under both interpreters
python2 -m pytest tests/ -v
python3 -bb -m pytest tests/ -v

# Step 6: Check for remaining Py2-only patterns
pylint --py3k src/

# Step 7: Verify with type checking (optional but recommended)
pip install mypy
mypy --python-version 3.12 src/
```

### Python: Handling bytes/str boundary correctly

> Full script: [python-handling-bytes-str-boundary-correctly.py](scripts/python-handling-bytes-str-boundary-correctly.py) (27 lines)

```python
# Input:  Code that processes both text and binary data
# Output: Python 3 compatible version with explicit type handling
import json
import hashlib
def process_api_response(raw_response: bytes) -> dict:
# ... (see full script)
```

### Python: Using LibCST for modern code transformation (Python 3.13+)

```python
# Input:  Python 2-style code patterns to modernize
# Output: Automated refactoring using LibCST (replaces 2to3 on Python 3.13+)

# Install: pip install libcst
import libcst as cst

# LibCST parses code as a concrete syntax tree (preserves formatting)
# Use it to build custom codemods for patterns futurize does not cover [src8]
# Example: transform old-style string formatting to f-strings
source = 'msg = "Hello %s, you have %d items" % (name, count)'
tree = cst.parse_module(source)
# Apply codemod transformations via libcst.codemod framework
# See: https://libcst.readthedocs.io/en/latest/codemods_tutorial.html
```

## Anti-Patterns

### Wrong: Using bare except with comma syntax

```python
# BAD — Python 2 exception syntax fails in Python 3
try:
    result = int(user_input)
except ValueError, e:
    print "Invalid input:", e
```

### Correct: Use 'as' keyword and print function

```python
# GOOD — Works in both Python 2.7+ and Python 3
from __future__ import print_function
try:
    result = int(user_input)
except ValueError as e:
    print("Invalid input:", e)
```

### Wrong: Relying on implicit str/bytes mixing

```python
# BAD — Works in Python 2 but raises TypeError in Python 3
def build_header(name, value):
    return b"Header: " + name + b"=" + value  # name/value might be str
```

### Correct: Explicitly encode/decode at boundaries

```python
# GOOD — Explicit bytes handling for Python 3 [src1, src3]
def build_header(name: str, value: str) -> bytes:
    header_str = "Header: {}={}".format(name, value)
    return header_str.encode("utf-8")
```

### Wrong: Using dict.has_key() and iteritems()

```python
# BAD — has_key() and iteritems() removed in Python 3
if config.has_key("timeout"):
    for key, val in config.iteritems():
        process(key, val)
```

### Correct: Use 'in' operator and .items()

```python
# GOOD — Works in both Python 2.7 and Python 3 [src2]
if "timeout" in config:
    for key, val in config.items():
        process(key, val)
```

### Wrong: Assuming division returns integer

```python
# BAD — Returns 2 in Python 2, returns 2.5 in Python 3
def calculate_average(total, count):
    return total / count  # Unexpected float in Python 3
```

### Correct: Use explicit floor division or true division

```python
# GOOD — Explicit about intent [src1]
from __future__ import division

def calculate_average(total, count):
    return total / count    # Always returns float (true division)

def calculate_pages(total, per_page):
    return total // per_page  # Always returns int (floor division)
```

### Wrong: Version-checking with equality

```python
# BAD — Breaks if Python 4 ever exists [src1]
import sys
if sys.version_info[0] == 3:
    from configparser import ConfigParser
else:
    from ConfigParser import ConfigParser
```

### Correct: Use feature detection (try/except)

```python
# GOOD — Feature detection is future-proof [src1]
try:
    from configparser import ConfigParser
except ImportError:
    from ConfigParser import ConfigParser
```

### Wrong: Using 2to3 on Python 3.13+

```python
# BAD — 2to3 was removed in Python 3.13. This command will fail.
# $ 2to3 -w mypackage/
# ModuleNotFoundError: No module named 'lib2to3'
```

### Correct: Use futurize or LibCST instead

```bash
# GOOD — futurize works on all Python versions [src7, src8]
pip install future
futurize --stage1 --stage2 -w mypackage/

# Or use LibCST for advanced transformations on Python 3.13+
pip install libcst
python -m libcst.tool codemod mypackage/
```

## Common Pitfalls

- **String/bytes TypeError at runtime**: Python 3 raises `TypeError` when you concatenate `str` and `bytes` (e.g., `"text" + b"data"`). In Python 2 this worked silently via implicit ASCII encoding. Fix: explicitly `.encode()` or `.decode()` at every I/O boundary. Run tests with `python3 -bb`. [src1, src3]
- **Integer division behavior change**: `5 / 2` returns `2` in Python 2 but `2.5` in Python 3. This causes subtle bugs in index calculations and pagination. Fix: add `from __future__ import division` to all files and use `//` for intentional floor division. [src1, src5]
- **dict.keys()/values()/items() return views, not lists**: Code that indexes into these results (`dict.keys()[0]`) fails in Python 3. Fix: wrap in `list()` where random access is needed, or iterate directly. [src2, src6]
- **bytes indexing returns int, not single-byte string**: `b"abc"[0]` returns `97` (int) in Python 3, not `b"a"`. Fix: use `b"abc"[0:1]` for a bytes slice, or `chr(b"abc"[0])` for a character. [src1]
- **print statement vs function**: Forgetting parentheses is a `SyntaxError` in Python 3. Fix: add `from __future__ import print_function` as the first migration step in every file. [src2]
- **Removed modules and renamed builtins**: `reduce` moved to `functools.reduce`, `reload` to `importlib.reload`, `unicode` to `str`, `long` to `int`. Fix: run futurize to catch most renames; check remaining `ImportError`s manually. [src2, src4]
- **sorted() and comparison operators**: Python 3 removed implicit comparisons between incompatible types (e.g., `None < 1` raises `TypeError`). Fix: add explicit `key=` functions to `sorted()` calls and handle `None` values before comparison. [src5]
- **csv module encoding change**: Python 2 csv requires binary mode (`'rb'`); Python 3 csv requires text mode with `newline=''`. Fix: use `open(f, 'r', newline='', encoding='utf-8')` for Python 3. [src5]
- **2to3 removed in Python 3.13**: Developers still relying on `2to3` or `lib2to3` will hit `ModuleNotFoundError` on Python 3.13+. Fix: switch to `futurize` or `LibCST` for automated code transformation. [src2, src8]
- **PEP 594 stdlib module removals in Python 3.13**: Twenty legacy modules were removed (aifc, audioop, cgi, cgitb, chunk, crypt, imghdr, mailcap, mhlib, nntplib, ossaudiodev, pipes, sndhdr, spwd, sunau, telnetlib, uu, xdrlib). Fix: find modern replacements before targeting Python 3.13+. [src1]

## Diagnostic Commands

> Full script: [diagnostic-commands.sh](scripts/diagnostic-commands.sh) (26 lines)

```bash
# Check current Python version
python --version && python3 --version
# Run Python 2 with Python 3 deprecation warnings
python2 -3 -Werror script.py
# Run Python 3 with bytes/str mixing detection
# ... (see full script)
```

## Version History & Compatibility

| Version | Status | Breaking Changes | Migration Notes |
|---|---|---|---|
| Python 3.14 (Oct 2025) | Current | `from __future__ import annotations` deprecated (PEP 649); incremental GC | Deferred annotation evaluation is now native; template strings added |
| Python 3.13 (Oct 2024) | Stable | `2to3` / `lib2to3` removed; PEP 594 removes 20 stdlib modules | Use `futurize` or LibCST instead; check for removed modules [src2] |
| Python 3.12 (Oct 2023) | Stable | Improved error messages; `2to3` still available but deprecated | Last Python version with `2to3` in stdlib |
| Python 3.11-3.12 | Stable | `2to3` deprecated (DeprecationWarning) | Still usable but migrate to alternative tools |
| Python 3.10 | Security | `collections.abc` must be imported explicitly | `from collections.abc import Mapping` not `from collections` |
| Python 3.8-3.9 | EOL | Assignment expressions (`:=`), positional-only params | Stable target for most migrations |
| Python 3.6-3.7 | EOL | f-strings, `dict` insertion order guaranteed (3.7) | Minimum recommended target for new migrations |
| Python 2.7 | EOL (Jan 2020) | Last Python 2 release ever | Required as migration source baseline [src1] |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Your codebase runs Python 2.7 | Code is already Python 3 only | No migration needed |
| Python 2 EOL means no security patches [src4] | Embedded system with frozen Python 2 runtime | Keep Python 2, isolate from network |
| You need Python 3 features (async/await, f-strings, type hints) [src6] | Throwaway script you'll never run again | Leave as-is |
| Third-party dependencies have dropped Python 2 support | Migration blocked by a dependency with no Py3 support | Fork/replace the dependency first, then migrate |
| Team hiring -- Python 3 developers are the norm [src4] | Legacy system being decommissioned within 6 months | Run out the clock on Python 2 |
| Performance improvement potential (12% CPU, 30% memory -- Instagram) [src4, src6] | Code is <100 lines and trivially rewriteable | Rewrite from scratch in Python 3 |

## Important Caveats

- Python 2 reached end-of-life on January 1, 2020 (PEP 373). No security patches, bug fixes, or updates are available. Running Python 2 in production is a security risk. [src1]
- The `2to3` tool was deprecated in Python 3.11 and removed entirely in Python 3.13 (October 2024). Use `futurize` (from `python-future`) or `python-modernize` (based on `six`) instead. For advanced transformations, use LibCST. [src2, src8]
- The `from __future__ import unicode_literals` import can cause unexpected issues with APIs that expect `bytes` (e.g., some C extensions, `os.path` on Python 2 Windows). Test thoroughly before adding globally. [src1, src3]
- Dropbox's migration of 1M+ lines took approximately 2 years from initial exploration to full rollout, with string encoding being the dominant challenge. Plan accordingly for large codebases. [src3]
- Running `python3 -bb` is essential during testing -- it converts `BytesWarning` to errors, catching implicit bytes/str mixing that would otherwise be silent. [src1]
- Dictionary ordering is guaranteed (insertion order) only from Python 3.7+. If targeting 3.6, use `collections.OrderedDict`. [src6]
- Python 3.13 removed 20 standard library modules under PEP 594 (dead batteries removal). If your Python 2 code imports `cgi`, `crypt`, `imghdr`, or other removed modules, you need modern replacements. [src1]
- `from __future__ import annotations` (PEP 563) was deprecated in Python 3.14 in favor of PEP 649's native deferred evaluation. While still functional, plan for its eventual removal (not before 2029). [src1]
- Python 3.10 reaches end-of-life in October 2026. Target Python 3.11 or newer for any net-new migration to avoid an immediate second upgrade cycle. [src1]

## Related Units

- [jQuery to React Migration](/software/migrations/jquery-to-react/2026)
- [JavaScript to TypeScript Migration](/software/migrations/javascript-to-typescript/2026)
- [Webpack to Vite Migration](/software/migrations/webpack-to-vite/2026)
