Lara
Lara

Reputation: 3174

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.

Upvotes: 122

Views: 141930

Answers (5)

user459872
user459872

Reputation: 24602

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 

EDIT: Mypy now does support this syntax.

Upvotes: 46

Sunding Wei
Sunding Wei

Reputation: 2214

Python 3.10+

It's done in modern Python 3.10

# Create type alias 'Array', which is an alias for a list.
Array = list

# Now we can use the 'Array' type to create a new object and use it like a list
a = Array()
a.append(1)
a.append(2)
a.append(3)

Upvotes: -6

Niel Godfrey P. Ponciano
Niel Godfrey P. Ponciano

Reputation: 10709

Python 3.5+

The accepted answer from @Lukasz is what we would need for most of the time. But for cases where you need the alias to be a distinct type on its own, you might need to use typing.NewType as documented here: https://docs.python.org/3/library/typing.html#newtype

from typing import List, NewType

Vector = NewType("Vector", List[float])

One particular use case is if you are using the injector library and you need to inject the aliased new type rather than the original type.

from typing import NewType

from injector import inject, Injector, Module, provider

AliasRawType = str
AliasNewType = NewType("AliasNewType", str)


class MyModule(Module):
    @provider
    def provide_raw_type(self) -> str:
        return "This is the raw type"

    @provider
    def provide_alias_raw_type(self) -> AliasRawType:
        return AliasRawType("This is the AliasRawType")

    @provider
    def provide_alias_new_type(self) -> AliasNewType:
        return AliasNewType("This is the AliasNewType")


class Test1:
    @inject
    def __init__(self, raw_type: str):  # Would be injected with MyModule.provide_raw_type() which is str. Expected.
        self.data = raw_type


class Test2:
    @inject
    def __init__(self, alias_raw_type: AliasRawType):  # Would be injected with MyModule.provide_raw_type() which is str and not MyModule.provide_alias_raw_type() which is just a direct alias to str. Unexpected.
        self.data = alias_raw_type


class Test3:
    @inject
    def __init__(self, alias_new_type: AliasNewType): # Would be injected with MyModule.provide_alias_new_type() which is a distinct alias to str. Expected.
        self.data = alias_new_type


injector = Injector([MyModule()])
print(injector.get(Test1).data, "-> Test1 injected with str")
print(injector.get(Test2).data, "-> Test2 injected with AliasRawType")
print(injector.get(Test3).data, "-> Test3 injected with AliasNewType")

Output:

This is the raw type -> Test1 injected with str
This is the raw type -> Test2 injected with AliasRawType
This is the AliasNewType -> Test3 injected with AliasNewType

Thus to correctly inject the proper provider when using the injector library, you would need the NewType aliasing.

Upvotes: 36

Jundiaius
Jundiaius

Reputation: 7630

Python 3.10+

Since Python 3.10, the TypeAlias annotation is available in the typing module.

It is used to explicitly indicate that the assignment is done to generate a type alias. For example:

Point: TypeAlias = tuple[float, float]
Triangle: TypeAlias = tuple[Point, Point, Point]

You can read more about the TypeAlias annotation on the PEP 613 that introduced it.

Upvotes: 102

Łukasz Rogalski
Łukasz Rogalski

Reputation: 23223

Python 3.5+

Since Python 3.5 you may use typing module.

Quoting docs, A type alias is defined by assigning the type to the alias:

# Python 3.5-3.8
from typing import List
Vector = List[float]

# Python 3.9+
Vector = list[float] # No import needed, lower case l

To learn more about enforcing types in Python you may want to get familiar with PEPs: PEP483 and PEP484.

Python historically was using duck-typing instead of strong typing and hadn't built-in way of declaring types before 3.5 release.

Upvotes: 208

Related Questions