78

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.

MEMark
  • 1,493
  • 2
  • 22
  • 32
Lara
  • 2,594
  • 4
  • 24
  • 36
  • 1
    Related: https://stackoverflow.com/questions/52504347/type-hints-is-it-a-bad-practice-to-alias-primitive-data-types – mic Apr 02 '20 at 18:57
  • what aout something super simple like aliasing Identifier to int, woulf `ID = int` work? – Charlie Parker Mar 17 '21 at 19:39

4 Answers4

157

Since Python 3.5 you may use typing module.

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

Vector = List[float]

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.

gerardw
  • 5,822
  • 46
  • 39
Łukasz Rogalski
  • 22,092
  • 8
  • 59
  • 93
  • 6
    Note that type hints are intended for static analysis and for use by linters. Type hints are not used for run-time type checking. Nor are they used for optimization. Also note that type hints are in their infancy. Almost no code in Python has type hints and no analysis tools have been developed to deal with them (as far as I know). – Steven Rumbalski Oct 09 '15 at 18:59
  • 1
    From the [Non-Goals section](https://www.python.org/dev/peps/pep-0484/#non-goals) of PEP484: "While the proposed typing module will contain some building blocks for runtime type checking -- in particular the get_type_hints() function -- third party packages would have to be developed to implement specific runtime type checking functionality, for example using decorators or metaclasses. Using type hints for performance optimizations is left as an exercise for the reader." – Steven Rumbalski Oct 09 '15 at 19:02
  • 1
    @StevenRumbalski: You're forgetting [mypy](http://mypy-lang.org/) (which is still in development, but arguably got this whole ball rolling to begin with). – Kevin Oct 09 '15 at 22:48
  • 4
    I had one function that returned a `Union[List[Dict[str, str]], List[Dict[str, Union[Dict[str, str], str]]]]`. Your suggestion worked wonders for me in cleaning it up nicely. – grooveplex Apr 10 '19 at 15:44
  • 1
    On Jan 2020, [PEP 613](https://www.python.org/dev/peps/pep-0613/) was created for special type alias syntax and it's currently in accepted status. I don't know if it's implemented yet. – Peeyush Kushwaha Aug 18 '20 at 16:06
  • 1
    What is the difference to `Vector = typing.NewType("Vector", List[float])`? – Martin Thoma Aug 31 '20 at 08:43
  • what aout something super simple like aliasing Identifier to int, woulf `ID = int` work? – Charlie Parker Mar 17 '21 at 19:38
  • 1
    @CharlieParker https://stackoverflow.com/a/52512686/11316205 explained possible problems with that approach – Nuclear03020704 Apr 29 '21 at 18:21
  • Python STILL uses duck typing. There's no strong typing built into Python. You can use external tools to check types (statically or on runtime). – michcio1234 Jun 10 '21 at 09:08
  • This answer is not factually wrong but won't really help in practice with most of type checkers. For instance PyCharm will always tell that the type of the variable is List[float] and never Vector so you cannot really use this approach if the goal is to give more code readabilty. I recommend strongly to use NewType as indicated below which solves all issues – ThR37 Jan 21 '23 at 21:49
45

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.

Jundiaius
  • 6,214
  • 3
  • 30
  • 43
  • 1
    Shouldn't `Tuple` be in lowercase, though? I thought I've seen that in some of the more recent PEPs. – 303 Mar 15 '22 at 20:39
  • 1
    Indeed! I will correct it. – Jundiaius Mar 28 '22 at 11:25
  • 6
    Actually, `tuple[...]` in lowercase is a new(-ish) feature. `Tuple` is compatible with at least Python 3.6. So if using Python < 3.10 one can still annotate explicit type aliases as `Point: 'TypeAlias' = Tuple[float, float]` – MestreLion Jun 07 '22 at 12:33
27

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.

  • Thanks, that's the best answer by far here. I was using "classic" type aliases as explained in the other answer but it was not actually recognized as a type by the type checker. This one works!! – ThR37 Jan 21 '23 at 21:50
1

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

type Point = tuple[float, float]

Type Aliases can also be generic.

type Point[T] = tuple[T, T]
Abdul Niyas P M
  • 18,035
  • 2
  • 25
  • 46