Search code examples
pythonpython-typingmypy

TypeVar defined within if/else


While this code works:

from typing import TypeAlias, TypeVar

T = TypeVar("T", complex, float, str)

z: TypeAlias = tuple[T, ...] | list[T]

defining T conditionally does not.

from typing import Generic, TypeAlias, TypeVar

try:
    import somepackage
    use_complex = True
except:
    use_complex = False

if use_complex:
    T = TypeVar("T", complex, float, str)
else:
    T = TypeVar("T", float, str)

z: TypeAlias = tuple[T, ...] | list[T]

I am getting: Variable not allowed in type expression

Is there a way to tell the typechecker that use_complex is a constant and therefore it is either one branch or the other, T is defined only once and does not change?


Solution

  • mypy and Pyright1 both support defining compile-time constants to enable control flow analysis like this; the behaviour is similar to sys.version_info, sys.platform, or typing.TYPE_CHECKING guards.

    Put the following in your mypy configuration file:

    [mypy]
    
    # Only specify ONE of the following:
    
    # * When developing against the package
    always_true = use_complex
    
    # * When not developing against the package
    always_false = use_complex
    

    Then do one of the following:

    • Define the constant in a dedicated module

      # constants.py
      
      try:
          import somepackage
          use_complex = True
      except:
          use_complex = False
      
      >>> import constants
      >>>
      >>> if constants.use_complex: ...
      ...     ...
      
    • Define the constant in the same module and use it. This is the same as your example in the question (mypy playground demo: always_true = use_complex, always_false = use_complex)

      try:
           import somepackage
           use_complex = True
      except:
           use_complex = False
      
      if use_complex:
           T = TypeVar("T", complex, float, str)
      else:
           T = TypeVar("T", float, str)
      
      z: TypeAlias = tuple[T, ...] | list[T]
      
      num: z[complex]  = [8j]  # OK when `always_true = use_complex`, else errors when `always_false = use_complex`
      

    During development, you'd just change your configuration file to switch between the use_complex modes.

    Note that the try...except doesn't actually do anything for mypy here, that's only for your runtime behaviour.


    1. See the defineConstant configuration of https://github.com/microsoft/pyright/blob/main/docs/configuration.md