Search code examples
pythonpython-typingtype-alias

How do you alias a type in Python?


In some (mostly functional) languages you can do something like this:

type row = list(datum)

or

type row = [datum]

So that we can build things like this:

type row = [datum]
type table = [row]
type database = [table]

Is there a way to do this in Python? You could do it using classes, but Python has quite some functional aspects so I was wondering if it could be done an easier way.


Solution

  • Python 3.12+

    Python 3.12 contains the implementation of PEP 695: Type Parameter Syntax which provides new way to declare type aliases using the type statement(similar to TypeScript).

    type Point = tuple[float, float]

    Type Aliases can also be generic:

    type Point[T] = tuple[T, T]

    The values of type aliases created through the type statement are lazily evaluated.

    Quoting from the documentation:

    The values of type aliases created through the type statement are lazily evaluated. The same applies to the bounds and constraints of type variables created through the type parameter syntax. This means that they are not evaluated when the type alias or type variable is created. Instead, they are only evaluated when doing so is necessary to resolve an attribute access.

    Example:

    >>> type Alias = 1/0
    >>> Alias.__value__
    Traceback (most recent call last):
      ...
    ZeroDivisionError: division by zero
    >>> def func[T: 1/0](): pass
    >>> T = func.__type_params__[0]
    >>> T.__bound__
    Traceback (most recent call last):
      ...
    ZeroDivisionError: division by zero
    

    Here the exception is raised only when the __value__ attribute of the type alias or the __bound__ attribute of the type variable is accessed.

    This behavior is primarily useful for references to types that have not yet been defined when the type alias or type variable is created. For example, lazy evaluation enables creation of mutually recursive type aliases:

    from typing import Literal
    
    type SimpleExpr = int | Parenthesized
    type Parenthesized = tuple[Literal["("], Expr, Literal[")"]]
    type Expr = SimpleExpr | tuple[SimpleExpr, Literal["+", "-"], Expr]
    

    Lazily evaluated values are evaluated in annotation scope, which means that names that appear inside the lazily evaluated value are looked up as if they were used in the immediately enclosing scope.

    Please note that support for PEP 695 in mypy is still under active development. But pyright did support this syntax by specifying -pythonversion 3.12 parameter.

    For example, running pyright on the following snippet using pyright myscript.py --pythonversion 3.12

    # myscript.py
    type vector = list[float]
    
    def foo(items: vector):
        pass
    
    foo(["foo", "bar"])
    

    will produce the following type error:

    myscript.py:6:5 - error: Argument of type "list[str]" cannot be assigned to parameter "items" of type "vector" in function "foo"
        "Literal['foo']" is incompatible with "float"
        "Literal['bar']" is incompatible with "float" (reportGeneralTypeIssues)
    1 error, 0 warnings, 0 informations