Linting

Linting#

Some approaches allow you to specify and check the validity of the code before executing it.

For more details check the Static Typing with Pythonq.

Annotations#

Python allows you to annotate types of varibles. By default, annotations don’t influence the program; they are just hints for static code checkers, such as linters.

Check more at the Annotations page.


The following cell shows the really basics of the annotations syntax.

val: int
lst: list[int]

def function(arg: int, arg2: str) -> int:
    return 5

Here:

  • val: is just a variable annotated as an int.

  • lst: is a list of integers, such kind of type called “generic”. Feature is that list parameterized with int.

  • function: specifies the types of the arguments and has an output of type int.

Types#

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

int, str, bool, float, None

Built-in types

x: int = 5

Union

typing.Union[A, B] / A | B (3.10+)

One of several types

x: int | None or x: Union[int, None]

Optional

typing.Optional[T]

Shorthand for Union[T, None]

x: Optional[str] = None

List

typing.List[T] / list[T] (3.9+)

A list of items

names: list[str]

Tuple

typing.Tuple[<types>] / tuple

A fixed-size tuple

point: Tuple[int, int]

Dict

typing.Dict[K, V] / dict[K, V] (3.9+)

Dictionary with keys and values

user: dict[str, int]

Set

typing.Set[T] / set[T]

A set of items

tags: set[str]

Callable

typing.Callable[[Args], Return]

A function signature

f: Callable[[int, str], bool]

Literal

typing.Literal[val1, val2]

Restrict to specific literal values

mode: Literal["r", "w"]

Any

typing.Any

Skip type checking

data: Any

None

None

Variable takes None value

def void() -> None

NoReturn

typing.NoReturn

Function never returns

def fail() -> NoReturn:

Annotated

typing.Annotated[T, metadata]

Attach metadata to a type

x: Annotated[int, "positive"]

NewType

typing.NewType('Name', BaseType)

Create a distinct type based on an existing one

UserId = NewType('UserId', int)

TypeVar

typing.TypeVar('T')

Generic type variable

def f(x: T) -> T:

Generic

typing.Generic[T]

Base class for generic classes

class Box(Generic[T]):

Self

typing.Self (3.11+)

Represents the current enclosed class

def clone(self) -> Self:

ClassVar

typing.ClassVar[T]

Type of class-level variables

version: ClassVar[str] = "1.0"

Final

typing.Final

Marks values as constants

MAX_SIZE: Final[int] = 100

TypedDict

class Movie(typing.TypedDict):

Dict with specific key types

title: str; year: int

Protocol

class P(typing.Protocol):

Structural subtyping (duck typing)

def read(self) -> bytes:

Union with ellipsis

tuple[int, ...]

Homogeneous tuple of any length

values: Tuple[int, ...]

Predefinition

val: 'SomeType'

If the type isn’t defined yet use literal as annotation

T = list[tuple['T', 'T']]

Check more accurate description with examples on the conrresponding page.

Tools#

There is the set of tools that designed to organize the process of annotations verification:

  • mypy: static type checking for python.

  • pyright: static type checking for python.

  • typeguard: runtime type checking.

  • flake8: code style checker.

For more details, check Tools page.

Mypy#

Tool that allows you to check if the programme respects the specified type hints.


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.

import typeguard
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