.. role:: python(code) :language: python .. index:: type hints; abstract type .. `abstract type first `_ `abstract type second `_ `abstract type third `_ .. _`sec:abstract_types`: Abstract types --------------- You already encountered the basic syntax. Let's focus here on more advanced concepts. Until now, only basic built-in types have been used: :python:`int, float, list[...], dict[..., ...]`, etc. The only more abstract type which has been used has been :python:`collections.abc.Callable`. This type declares that the parameter supports a **specific behavior**: being called (potentially with input and output arguments). Several types allow to be called. As long as the type behaves as intended, it is all that matter. Only relying on the behavior is called **duck typing** at runtime and **structural sub-typing** when applied statically (before running). :python:`collections.abc` contains the `built-in types `_. If several potential types might be expected, use ``|``. .. note:: Use abstract types for input arguments, however, use concrete types for output arguments if possible: you only want to enforce a behavior for inputs, however, you usually know what you are returning. .. admonition:: Exercise (easy) Complete the type hints. Write some example calls to see the type checker in action. .. code-block:: python :linenos: from collections.abc import ... def grade_exam(question_grades: ...) -> ...: """question_grades stores the grade for every question""" return sum(question_grades) .. _`ex:abstract_type_iterable`: .. collapse:: Correction .. code-block:: python :linenos: from collections.abc import Iterable def grade_exam(question_grades: Iterable[int|float]) -> int|float: """question_grades stores the grade for every question""" return sum(question_grades) grade: float = grade_exam((1, 1.5)) grade_: int = grade_exam((1, 1.5)) # not ok grade_exam((1, "1.5")) # not ok .. .. admonition:: Exercise (easy) .. Complete the type hints. Write some example calls to see the type checker in action. .. .. code-block:: python .. :linenos: .. from collections.abc import ... .. from typing import ... .. # each group gathers the 'identifiers' of every student .. # an identifier might be anything: int, custom object etc .. def get_group_sizes(groups: ...) -> ...: .. return [len(students) for students in groups] .. .. collapse:: Correction .. .. code-block:: python .. :linenos: .. from collections.abc import Collection .. from typing import Any .. def get_group_sizes(groups: Iterable[Collection[Any]]) -> list[int]: .. return [len(students) for students in groups] .. get_group_sizes({("a", "b"), (1, 2, 3)}) .. get_group_sizes({1, 2}) # not ok .. admonition:: Exercise (intermediate) Complete the type hints. Pay attention to the brackets in the return statement. Write some example calls to see the type checker in action. .. code-block:: python :linenos: from collections.abc import ... def select_adult_students(student_ages: ...) -> ...: """student_ages stores key(name):value(age) pairs""" return (name for name, age in student_ages.items() if age > 18) def select_adult_students_(student_ages: ...) -> ...: """student_ages stores key(name):value(age) pairs""" for name, age in student_ages.items(): if age > 18: yield name .. _`ex:abstract_type_generator`: .. collapse:: Correction .. code-block:: python :linenos: from collections.abc import Mapping, Generator def select_adult_students(student_ages: Mapping[str, int]) -> Generator[str]: return (name for name, age in student_ages.items() if age > 18) adults: tuple[str, ...] = tuple(select_adult_students({"a": 17})) adults_: list[str] = select_adult_students({"a": 17}) # not ok .. admonition:: Exercise (intermediate) Complete the type hints. .. code-block:: python :linenos: from collections.abc import Callable, Sequence def admit_student( one_student_names: ..., # every names of a student in order has_paid_fees: ..., generate_unique_email: ..., ) -> ...: if has_paid_fees(one_student_names): return generate_unique_email(one_student_names) return None .. _`ex:abstract_type_callable`: .. collapse:: Correction .. code-block:: python :linenos: from collections.abc import Callable, Sequence def admit_student( one_student_names: Sequence[str], # every names of a student in order has_paid_fees: Callable[[Sequence[str]], bool], generate_unique_email: Callable[[Sequence[str]], str], ) -> str | None: if has_paid_fees(one_student_names): return generate_unique_email(one_student_names) return None email: str|None = admit_student( ["firstname", "lastname"], lambda _: True, lambda _: "firstname.lastname@domain.fr", ) email_: str|None = admit_student( ["firstname", "lastname"], lambda _: "True", # not ok lambda _: "firstname.lastname@domain.fr", )