How to create a user-defined type assertion in Python?

How can one create a function that narrows the type of a variable (for static type checkers) in a similar way to isinstance?

For example, ComplexTypeAssertion just narrows the type at runtime but not for static checks:

def MyFunction(myData: object) -> object:
  if isinstance(myData, list):
    reveal_type(myData)  # Revealed type is 'builtins.list[Any]'
  if ComplexTypeAssertion(myData):   # <<< How to make MyPy understand this?
    reveal_type(myData)  # Revealed type is 'builtins.object'

def ComplexTypeAssertion(data: object) -> bool:
    return isinstance(data, list) and all(isinstance(value, str) for value in data)

How could I define ComplexTypeAssertion such that static analysis tools would understand the type?

Obviously, this is a toy example, real life examples would be more complex. It would be very useful in situations where I want to assert that some data follows a TypedDict construct or other typing constructs.


Mypy generally only understands a few specific ways of narrowing a type, such as using isinstance or if x is not None. However, in Python >=3.10, PEP 647 provides a solution to this problem with the new typing.TypeGuard feature. Now, you are able to write your ComplexTypeAssertion function like so, and type checkers will understand that data is guaranteed to be of type list[str] if the function returns True:

from typing import TypeGuard

def ComplexTypeAssertion(data: object) -> TypeGuard[list[str]]:
    return isinstance(data, list) and all(isinstance(value, str) for value in data)

TypeGuard is also available for Python <3.10 via the semi-official typing-extensions library on PyPI, which provides backports of newer typing constructs to earlier versions of Python. (If you're using Mypy, you'll find that typing-extensions is already a dependency of Mypy, so importing something from typing-extensions won't even necessarily mean that you have to add an additional dependency to your project.)