Type annotations#
Python has an option to annotate types, which is useful during relatively serious development.
Check resources:
Python type checking article on real python.
PEP 484 that introduce type hints.
PEP 526 that introduce variable annotations.
import sys
import typeguard
sys.path.append("/tmp")
Usecases#
In general, you can define types of parameters and return values for the function and specify the type for some variable/attribute. This doesn’t affect the behavior of the interpreter in any way, but different code analisys tools make use of it.
The following cell creates python file that defines function that expect to take float
and int
and return bool
. The function is wrapped with typeguard.typechecked
decorator, which throws raise an error every time it is called with wrong types.
%%writefile /tmp/typed_fun.py
from typeguard import typechecked
@typechecked
def some_function(arg1: float, arg2: int) -> bool:
return arg1 > arg2
Overwriting /tmp/typed_fun.py
The following code shows the exception you get when you try to pass parameters of the wrong type to the function.
from typed_fun import some_function
try: some_function(5.5, 3.3)
except Exception as e: print(e)
argument "arg2" (float) is not an instance of int
The next cell generates a file that contains a variable that shoudl be of the datatype float
datatype, but assigns str
to the variable.
%%writefile /tmp/typed_var.py
pi: float = "test"
Overwriting /tmp/typed_var.py
mypy
static analisator throws corresponding error to that file.
!python3 -m mypy /tmp/typed_var.py
/tmp/typed_var.py:1: error: Incompatible types in assignment (expression has type "str", variable has type "float") [assignment]
Found 1 error in 1 file (checked 1 source file)
Annotation cases#
This section provided an overview of the various syntax and typing
attributes that can be used to define types.
Category |
Syntax |
Description |
Example |
---|---|---|---|
Basic types |
|
Built-in types |
|
Union |
|
One of several types |
|
Optional |
|
Shorthand for |
|
List |
|
A list of items |
|
Tuple |
|
A fixed-size tuple |
|
Dict |
|
Dictionary with keys and values |
|
Set |
|
A set of items |
|
Callable |
|
A function signature |
|
Literal |
|
Restrict to specific literal values |
|
Any |
|
Skip type checking |
|
None |
|
Variable takes |
|
NoReturn |
|
Function never returns |
|
Annotated |
|
Attach metadata to a type |
|
NewType |
|
Create a distinct type based on an existing one |
|
TypeVar |
|
Generic type variable |
|
Generic |
|
Base class for generic classes |
|
Self |
|
Return type of method returning its own class |
|
ClassVar |
|
Type of class-level variables |
|
Final |
|
Marks values as constants |
|
TypedDict |
|
Dict with specific key types |
|
Protocol |
|
Structural subtyping (duck typing) |
|
Union with ellipsis |
|
Homogeneous tuple of any length |
|
Check more accurate description with examples on the conrresponding page.
Type aliases#
Note that you can save your annotations as regular python object - your programmes will be neater.
Below is an example that defines a function to create triangles as a tuple of three two-dimensional points. To make things simplier, you can define an annotation for the point and use it to define an annotation for the triangle.
from random import random
# Here is straighforward definition
def get_triangle() -> tuple[
tuple[float, float],
tuple[float, float],
tuple[float, float]
]:
return (
(random(), random())
for i in range(3)
)
# Same but shorter
point = tuple[float, float]
def get_triangle() -> tuple[point, point, point]:
return (
(random(), random())
for i in range(3)
)
__annotations__
attribute#
The __annotations__
attribute allows you to retrieve annotated types for a Python object.
For function it returns a dictionary with keys corresponding to the names of the arguments and values corresponding to the types of the arguments. The return type of the function can be accessed using the return
key.
def some_function(arg1: float, arg2: bool) -> int:
if arg1 > 10 and arg2:
return 20
some_function.__annotations__
{'arg1': float, 'arg2': bool, 'return': int}
You can simply access it from any Python namespace to get annotations of the waribles in that namespace.
new_variable : float = 10
test_variable : float = 10
__annotations__
{'new_variable': float, 'test_variable': float}
Tools#
There are a few tools that designed to organize the process of annotations verification:
Mypy#
Tool that allows you to check if the programme respects the specified type hints.
Check more details on this tools in the corresponding page.
So in the following example a function is defined that takes two arguments and they are annotated as int
.
%%writefile /tmp/mypy_example.py
def bin_sum(a: int, b: int):
return a+b
print(bin_sum("a", "b"))
Writing /tmp/mypy_example.py
The following code shows shows how to apply mypy
to this file.
!python3 -m mypy /tmp/mypy_example.py
/tmp/mypy_example.py:4: error: Argument 1 to "bin_sum" has incompatible type "str"; expected "int" [arg-type]
/tmp/mypy_example.py:4: error: Argument 2 to "bin_sum" has incompatible type "str"; expected "int" [arg-type]
Found 2 errors in 1 file (checked 1 source file)
As a result, we got corresponding errors.
Typeguard#
Type guard checks whether the value matches to the annotated types at runtime. With this library you can:
Write code that checks if the value matches to specified type - use the
typeguard.check_type()
function.Modify the behavior of the functions using the
@typeguard.typechecked
decorator - so that any inconsistency in the input/output of the function will result in an error.
The following cell checks if the literal “hello” matches to the int
annotation, and shows what kind of error typeguard
will generate in such a case.
try: typeguard.check_type("hello", int)
except Exception as e: print(type(e), e)
<class 'typeguard.TypeCheckError'> str is not an instance of int
The following cell saves to file code that uses the @typeguard.typechecked
decorator, as it doesn’t work in jupyter notebook.
%%writefile /tmp/typechecked_decorator.py
import typeguard
@typeguard.typechecked
def value() -> int:
return "value"
Overwriting /tmp/typechecked_decorator.py
Function returns “value” but int
is annotated. The following cell shows what kind of error typeguard
will produce for such a case.
from typechecked_decorator import value
try: value()
except Exception as e: print(type(e), e)
<class 'typeguard.TypeCheckError'> the return value (str) is not an instance of int