Skip to content

Python 3.13 Migration Errors: How to Fix ImportError and Compatibility Issues

I upgraded to Python 3.13 last week, excited about the free-threading support and experimental JIT compiler. But within minutes, my CI pipeline was red:

Import Error
ImportError: cannot import name 'Counter' from 'collections'
ModuleNotFoundError: No module named 'numpy.core._multiarray_umath'

Three hours later, I had a working migration and a checklist I wish I’d had from the start.

The Migration Decision Tree

Before diving into fixes, here’s the mental model that helped me debug:

Migration Debug Flow
Start Migration
Run Tests ────────► All Pass? ────► ✓ Done
│ │
▼ No ▼ No
ImportError? Other Error
│ │
▼ ▼
Check PyPI Check Deprecation
Classifiers Warnings
│ │
▼ ▼
Update or Fix Code
Find Alt Patterns
│ │
└──────────────────────┘
Re-run Tests

The Pre-Migration Check I Skipped (And Regretted)

I jumped straight into creating a Python 3.13 virtual environment. Bad move.

Here’s what I should have done first:

check_compatibility.py
import pkg_resources
import requests
def check_python313_support():
"""Check if installed packages support Python 3.13"""
installed_packages = [d.project_name for d in pkg_resources.working_set]
for package in installed_packages:
try:
response = requests.get(f"https://pypi.org/pypi/{package}/json")
data = response.json()
classifiers = data.get('info', {}).get('classifiers', [])
python313_support = any('Python :: 3.13' in c for c in classifiers)
print(f"{package}: {'' if python313_support else ''}")
except Exception:
print(f"{package}: Unable to check")

Running this saved me from guessing which packages were incompatible:

Compatibility Check Output
requests: ✓
numpy: ✗
pandas: ✗
flask: ✓
redis: ✓

NumPy and pandas were blockers. I needed newer versions.

The ImportError Pattern

My first error was cryptic:

NumPy Import Error
ModuleNotFoundError: No module named 'numpy.core._multiarray_umath'

This happens when a package was compiled for an older Python version. NumPy uses C extensions that need recompilation for Python 3.13.

Why this happens: Python’s C ABI changes between major versions. Packages with compiled extensions need new wheels published to PyPI.

The fix:

requirements_313.txt
# Old versions (incompatible)
numpy==1.24.0
pandas==1.5.0
# New versions (Python 3.13 compatible)
numpy>=2.0.0
pandas>=2.2.0

After updating and reinstalling:

Terminal window
pip install -r requirements_313.txt

NumPy loaded. But then came the next error.

The Deprecated Import Pattern

This one surprised me:

Old Code (Deprecated)
import collections
counter = collections.Counter()

This worked in Python 3.12. In 3.13, it throws a warning that will become an error:

Deprecation Warning
DeprecationWarning: Using or importing the ABCs from 'collections'
instead of from 'collections.abc' is deprecated

The fix:

New Code (3.13 Compatible)
from collections import Counter
counter = Counter()

Or for abstract base classes:

ABC Import Fix
# Old (deprecated)
from collections import Mapping
# New (correct)
from collections.abc import Mapping

This pattern affects:

  • collections.Counter → Use from collections import Counter
  • collections.defaultdict → Use from collections import defaultdict
  • collections.Mapping → Use from collections.abc import Mapping
  • collections.MutableMapping → Use from collections.abc import MutableMapping

I had 47 files to update. A quick regex search and replace:

Find and Replace Pattern
# Find: from collections import (.*)
# Replace: from collections.abc import \1
# Then manually verify each match

The Missing Performance Boost

Python 3.13 promised performance improvements. I expected my data processing script to run faster.

It didn’t.

The problem: I assumed JIT was enabled by default. It isn’t.

Enable JIT (Optional)
# JIT is experimental and disabled by default
export PYTHON_JIT=1
python your_script.py

Or in code:

Enable JIT Programmatically
import sys
import sysconfig
# Check if JIT is available
print(f"JIT available: {sysconfig.get_config_var('Py_GIL_DISABLED')}")

Even with JIT enabled, my code didn’t benefit much. The reason: JIT optimizes hot loops and numeric code, but my script was I/O bound.

JIT Optimization Profile
JIT Friendly JIT Unfriendly
───────────── ──────────────
Hot loops I/O bound code
Numeric operations Network calls
Pure Python Extension modules
Repeated functions One-time operations

To actually benefit from JIT:

JIT-Friendly Pattern
# Before: List comprehension (JIT can optimize)
def process_data(data):
return [x * 2 for x in data]
# After: Explicit loop (better for JIT tracing)
def process_data(data):
result = []
for x in data:
result.append(x * 2)
return result

The JIT compiler traces the explicit loop better than the list comprehension.

Memory Usage Higher Than Expected

My application’s memory footprint increased after migration. The culprit: loading entire datasets into memory.

Memory Inefficient (Before)
def process_large_file(filepath):
with open(filepath) as f:
data = f.readlines() # Loads entire file into memory
for line in data:
yield process_line(line)

The fix: use generators and chunk processing.

Memory Efficient (After)
def process_large_file(filepath, chunk_size=1000):
"""Process file in chunks to reduce memory"""
with open(filepath) as f:
chunk = []
for i, line in enumerate(f):
chunk.append(process_line(line))
if len(chunk) >= chunk_size:
yield from chunk
chunk = []
if chunk: # Remaining lines
yield from chunk

Memory dropped from 2GB to 200MB for a 1GB file.

The Unit Test That Saved Me

Before deploying to production, I wrote a compatibility test:

test_compatibility.py
import unittest
import warnings
class Python313CompatibilityTest(unittest.TestCase):
def setUp(self):
warnings.simplefilter("always", DeprecationWarning)
def test_import_statements(self):
"""Test all module imports work correctly"""
try:
import your_main_module
self.assertTrue(True)
except ImportError as e:
self.fail(f"Import failed: {str(e)}")
def test_deprecated_features(self):
"""Check for deprecated feature usage"""
with warnings.catch_warnings(record=True) as w:
# Import your modules here
import your_main_module
# Trigger code paths
your_main_module.main()
if w:
for warning in w:
print(f"Warning: {warning.message}")
print(f" File: {warning.filename}:{warning.lineno}")

Running this before full deployment:

Terminal window
python -m pytest test_compatibility.py -v

This caught 3 deprecated imports I’d missed.

Common Pitfalls Checklist

Here’s what I learned to check before migration:

Pre-Migration Checklist
□ Run PyPI compatibility check script
□ Check for Python :: 3.13 classifiers
□ Update requirements.txt with compatible versions
□ Fix deprecated import patterns
- collections.X → from collections import X
- collections.abc imports
□ Run unit tests in Python 3.13 environment
□ Profile performance with and without JIT
□ Test memory usage with large datasets
□ Verify extension modules have Python 3.13 wheels
□ Check for deprecated stdlib removals

The pkg_resources Deprecation

One more thing: pkg_resources is deprecated in Python 3.13.

Replace pkg_resources
# Old (deprecated)
import pkg_resources
version = pkg_resources.get_distribution('requests').version
# New (recommended)
import importlib.metadata
version = importlib.metadata.version('requests')

The importlib.metadata module is the modern replacement and works across Python versions.

Summary

Python 3.13 migration errors fall into predictable patterns:

  1. Missing classifiers → Update package versions
  2. Deprecated imports → Fix import statements
  3. C extension incompatibility → Wait for wheels or compile from source
  4. Performance issues → Enable JIT, optimize for JIT-friendly patterns
  5. Memory bloat → Use generators and chunk processing

The key is checking compatibility before creating the Python 3.13 environment. The 5-minute compatibility script saves hours of debugging.

If a critical package lacks Python 3.13 support, you have three options:

  • Wait for the package maintainer to release a compatible version
  • Use an alternative package
  • Stay on Python 3.12 until the ecosystem catches up

For me, updating NumPy and pandas to their latest versions, fixing 47 deprecated imports, and adjusting my code for JIT-friendly patterns took a full afternoon. But now the migration is complete, and my CI pipeline is green.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments