1) from collections import X breaks (Mapping/MutableMapping/Iterable, etc.)

Symptoms

  • ImportError: cannot import name 'Mapping' from 'collections'
     

  • Similar errors for MutableMapping, Iterable, Sequence, etc.

Why

The ABCs stopped being re-exported from collections in 3.10. You must import them from collections.abc. This had been warned about for years and finally flipped in 3.10.

Fix (drop-in)

# old
from collections import Mapping, MutableMapping, Iterable

# new
from collections.abc import Mapping, MutableMapping, Iterable

Prevent it next time

  • Grep your code and templates for from collections import.
     

  • Add a linter rule or CI check that fails on from collections import (Mapping|MutableMapping|Iterable|...).
     

  • If a dependency still imports from collections, upgrade it or patch locally.

2) parser module removal

Symptoms

  • ModuleNotFoundError: No module named 'parser'

  • Tools that relied on old LL(1) parser internals stop working.

Why

CPython switched to the PEG parser in 3.9. The legacy parser module and related C APIs were removed in 3.10.

Fix / Migration paths

  • If you parsed Python code: use ast or modern libraries like LibCST / Parso.
     

  • If you generated/inspected bytecode: use ast, dis, or higher-level introspection APIs.
     

  • If you depended on lib2to3/pgen2 style grammars, plan a move - those won’t parse future grammar changes reliably.

Prevent it next time

  • Avoid relying on undocumented internals; prefer ast and well-maintained parsing libs.
     

  • Skim “Removed” sections of each What’s New before upgrading.

3) asyncio’s loop= parameter removal

Symptoms

  • TypeError: ... got an unexpected keyword argument 'loop'
     

  • Failing code like asyncio.Queue(loop=loop), asyncio.Lock(loop=loop), asyncio.gather(..., loop=loop).

Why

loop= had been deprecated; in 3.10 it’s removed from most high-level APIs. The event loop is now discovered via asyncio.get_running_loop(), or managed by asyncio.run().

Fix (typical patterns)

# old
loop = asyncio.get_event_loop()
q = asyncio.Queue(loop=loop)
lock = asyncio.Lock(loop=loop)

# new
async def main():
    q = asyncio.Queue()
    lock = asyncio.Lock()

asyncio.run(main())
  • Need the loop object? Inside a coroutine: loop = asyncio.get_running_loop().
     

  • Creating tasks: prefer asyncio.create_task() inside running code.

Prevent it next time

  • Keep your async style “modern”: asyncio.run, create_task, get_running_loop only when required.
     

  • In libs, avoid exposing loop juggling to users.

4) distutils deprecation warnings

Symptoms

  • Warnings like: distutils is deprecated and slated for removal in Python 3.12.

Why

PEP 632: distutils was deprecated in 3.10, fully removed in 3.12.

Fix (modern build backend)

# pyproject.toml
[build-system]
requires = ["setuptools>=64", "wheel"]
build-backend = "setuptools.build_meta"
  • Replace from distutils... imports with Setuptools or packaging equivalents.

Prevent it next time

  • Track PyPA and PEP updates; avoid stdlib packaging APIs slated for removal.

5) Smaller but notable gotchas

  • importlib migration (find_module → find_spec): Old patterns using pkgutil.find_loader/imp.find_module should move to importlib.util.find_spec.
     

  • C-API/Embedding changes: Read Python 3.10 C API changes if you maintain C extensions.

Upgrade playbook

  1. Run tests on 3.10 early (use tox/CI matrix).
     

  2. Fail on deprecations: pytest -W error::DeprecationWarning
     

  3. Automate fixes: replace from collections import with collections.abc, remove loop= in asyncio.
     

  4. Modernize packaging with pyproject.toml.
     

  5. Pin and upgrade dependencies.
     

  6. Read What’s New in 3.10 before rollout.