How to seamlessly support typing.Protocol on Python versions older and newer than 3.8. At the same time.

typing.Protocol has been added in Python 3.8. Therefore if want to use structural subtyping using it on both Python versions older and newer than 3.8 within the same code base, you need to get it from typing-extensions pre-3.8, and from typing on 3.8 and later.1

The most obvious – and arguably most Pythonic – way would be:

try:
    from typing import Protocol
except ImportError:
    from typing_extensions import Protocol

However, mypy will fail with a rather inscrutable error:

error: Incompatible import of "Protocol" (imported name has type "typing_extensions._SpecialForm", local name has type "typing._SpecialForm")

The way to guard imports from typing that mypy understands is to check the Python version:

import sys

if sys.version_info >= (3, 8):
    from typing import Protocol
else:
    from typing_extensions import Protocol

Therefore, the brand-new ParamSpec works like this:

import sys

if sys.version_info >= (3, 10):
    from typing import ParamSpec
else:
    from typing_extensions import ParamSpec

  1. If you want to prevent having an unnecessary dependency, that is. ↩︎