Type annotations
Topics: Type annotations
Updated 2020-10-04
Type annotations
Type annotations are a very cool feature that came out with Python 3.5. They allow us to add arbitrary metadata to function arguments and the return value of a function. Why this is useful? First of all, it allows us to document of what type our function parameters are. Furthermore, they can be used for things like type checking. For more use cases, look here.
Let’s add annotations to our ___init___
constructors:
class CastleKilmereMember:
""" Creates a member of the Castle Kilmere School of Magic """
def __init__(self, name: str, birthyear: int, sex: str):
...
class Professor(CastleKilmereMember):
""" Creates a Castle Kilmere professor """
def __init__(self, name: str, birthyear: int, sex: str, subject: str):
...
class Ghost(CastleKilmereMember):
""" Creates a Castle Kilmere ghost """
def __init__(self, name: str, birthyear: int, sex: str, year_of_death: int):
...
class Pupil(CastleKilmereMember):
""" Create a Castle Kilmere Pupil """
def __init__(self, name: str, birthyear: int, sex: str, start_year: int, pet=None):
...
Specifying the return type
It’s easy to specify the return value of a function. Remember the says
function of our class CastleKilmereMember
? It takes a string of words as an input and returns a string. This can be
specified as follows:
class CastleKilmereMember:
def __init__(self, name: str, birthyear: int, sex: str):
...
def says(self, words: str) -> str:
return f"{self.name} says: {words}"
We can also reference classes as a return value. Our classmethod school_headmistress
returns an instance of the CastleKilmereMember
class. We can specify this by using the name of the class as a type annotation for the return value:
class CastleKilmereMember:
...
@classmethod
def school_headmistress(cls) -> 'CastleKilmereMember':
return cls('Miranda Mirren', 1963, 'female')
We need to provide the class name in colons because the type annotation represents a forward reference. This behaviour will change with Python 3.10 where classes can be used as type annotations directly. We can get this new behaviour already with Python 3.7+ with the import from __future__ import annotations
:
from __future__ import annotations
class CastleKilmereMember:
...
@classmethod
def school_headmistress(cls) -> CastleKilmereMember:
return cls('Miranda Mirren', 1963, 'female')
Using nested type annotations
The pet
attribute our our Pupil class is given by a tuple which contains the name of the pet (a string) and its type (also a string). We can specify this using a nested type annotation:
from typing import Tuple
class Pupil(CastleKilmereMember):
def __init__(..., pet: Tuple[str, str]):
...
Simple datatypes like str
and int
can be used directly as type hints. For more complicated types like tuple
or list
we need to import them from the typing
module (example from typing import Tuple
). With Python 3.8+ we can use the types directly as type hints.
Where to put spaces when using type annotations
Since the post I mentioned above does not include whitespaces between the different parts of an annotation I checked the correct syntax of function annotations. As mentioned in PEP8 the proper usage of spaces is as follows:
Function annotations should use the normal rules for colons and always have spaces around the -> arrow if present.
Yes:
def munge(input: AnyStr): ...
def munge() -> AnyStr: ...
No:
def munge(input:AnyStr): ...
def munge()->PosInt: ...
When combining an argument annotation with a default value, use spaces around the = sign (but only for those arguments that have both an annotation and a default).
Yes:
def munge(sep: AnyStr = None): ...
def munge(input: AnyStr, sep: AnyStr = None, limit=1000): ...
No:
def munge(input: AnyStr=None): ...
def munge(input: AnyStr, limit = 1000): ...