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:
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:
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 TestsThe 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:
import pkg_resourcesimport 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:
requests: ✓numpy: ✗pandas: ✗flask: ✓redis: ✓NumPy and pandas were blockers. I needed newer versions.
The ImportError Pattern
My first error was cryptic:
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:
# Old versions (incompatible)numpy==1.24.0pandas==1.5.0
# New versions (Python 3.13 compatible)numpy>=2.0.0pandas>=2.2.0After updating and reinstalling:
pip install -r requirements_313.txtNumPy loaded. But then came the next error.
The Deprecated Import Pattern
This one surprised me:
import collectionscounter = collections.Counter()This worked in Python 3.12. In 3.13, it throws a warning that will become an error:
DeprecationWarning: Using or importing the ABCs from 'collections'instead of from 'collections.abc' is deprecatedThe fix:
from collections import Countercounter = Counter()Or for abstract base classes:
# Old (deprecated)from collections import Mapping
# New (correct)from collections.abc import MappingThis pattern affects:
collections.Counter→ Usefrom collections import Countercollections.defaultdict→ Usefrom collections import defaultdictcollections.Mapping→ Usefrom collections.abc import Mappingcollections.MutableMapping→ Usefrom collections.abc import MutableMapping
I had 47 files to update. A quick regex search and replace:
# Find: from collections import (.*)# Replace: from collections.abc import \1# Then manually verify each matchThe 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.
# JIT is experimental and disabled by defaultexport PYTHON_JIT=1python your_script.pyOr in code:
import sysimport sysconfig
# Check if JIT is availableprint(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 Friendly JIT Unfriendly ───────────── ────────────── Hot loops I/O bound code Numeric operations Network calls Pure Python Extension modules Repeated functions One-time operationsTo actually benefit from JIT:
# 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 resultThe 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.
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.
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 chunkMemory dropped from 2GB to 200MB for a 1GB file.
The Unit Test That Saved Me
Before deploying to production, I wrote a compatibility test:
import unittestimport 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:
python -m pytest test_compatibility.py -vThis caught 3 deprecated imports I’d missed.
Common Pitfalls Checklist
Here’s what I learned to check before migration:
□ 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 removalsThe pkg_resources Deprecation
One more thing: pkg_resources is deprecated in Python 3.13.
# Old (deprecated)import pkg_resourcesversion = pkg_resources.get_distribution('requests').version
# New (recommended)import importlib.metadataversion = 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:
- Missing classifiers → Update package versions
- Deprecated imports → Fix import statements
- C extension incompatibility → Wait for wheels or compile from source
- Performance issues → Enable JIT, optimize for JIT-friendly patterns
- 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