
Illustration de Magdalena Tomczyk
Dans la première partie de l'article, j'ai décrit les bases de l'utilisation des annotations de type. Cependant, plusieurs points importants n'ont pas été pris en compte. Premièrement, les génériques sont un mécanisme important, et deuxièmement, il peut parfois être utile de trouver des informations sur les types attendus lors de l'exécution. Mais je voulais commencer avec des choses plus simples
Annonce préalable
En règle générale, vous ne pouvez pas utiliser un type avant sa création. Par exemple, le code suivant ne démarre même pas:
class LinkedList: data: Any next: LinkedList
Pour résoudre ce problème, il est permis d'utiliser un littéral de chaîne. Dans ce cas, les annotations seront calculées différées.
class LinkedList: data: Any next: 'LinkedList'
Vous pouvez également accéder aux classes à partir d'autres modules (bien sûr, si le module est importé): some_variable: 'somemodule.SomeClass'
RemarqueDe manière générale, toute expression calculable peut être utilisée comme annotation. Cependant, il est recommandé de les garder aussi simples que possible afin que les utilitaires d'analyse statique puissent les utiliser. En particulier, ils ne comprendront probablement pas les types calculables dynamiquement. Pour en savoir plus sur les restrictions, cliquez ici: PEP 484 - Astuces de type # Astuces de type acceptables
Par exemple, le code suivant fonctionnera et même des annotations seront disponibles lors de l'exécution, mais mypy générera une erreur dessus
def get_next_type(arg=None): if arg: return LinkedList else: return Any class LinkedList: data: Any next: 'get_next_type()'
UPD : En Python 4.0, il est prévu d'inclure le calcul d'annotation de type différé ( PEP 563 ), qui supprimera cette technique avec les littéraux de chaîne. avec Python 3.7, vous pouvez activer un nouveau comportement à l'aide de la construction d' from __future__ import annotations
Fonctions et objets appelés
Pour les situations où vous devez transférer une fonction ou un autre objet (par exemple, en tant que rappel), vous devez utiliser l'annotation Callable [[ArgType1, ArgType2, ...], ReturnType]
Par exemple
def help() -> None: print("This is help string") def render_hundreds(num: int) -> str: return str(num // 100) def app(helper: Callable[[], None], renderer: Callable[[int], str]): helper() num = 12345 print(renderer(num)) app(help, render_hundreds) app(help, help)
Il est permis de spécifier uniquement le type de retour de la fonction sans spécifier ses paramètres. Dans ce cas, les points de suspension sont utilisés: Callable[..., ReturnType]
. Notez qu'il n'y a pas de crochets autour des points de suspension.
Pour le moment, il est impossible de décrire une signature de fonction avec un nombre variable de paramètres d'un certain type ou de spécifier des arguments nommés.
Types génériques
Parfois, il est nécessaire de sauvegarder des informations sur un type, sans le fixer de manière rigide. Par exemple, si vous écrivez un conteneur qui stocke les mêmes données. Ou une fonction qui renvoie des données du même type que l'un des arguments.
Des types tels que List ou Callable, que nous avons vu précédemment, utilisent simplement le mécanisme générique. Mais en plus des types standard, vous pouvez créer vos propres types génériques. Pour ce faire, premièrement, obtenez une variable TypeVar, qui sera un attribut du générique, et deuxièmement, déclarez directement un type générique:
T = TypeVar("T") class LinkedList(Generic[T]): data: T next: "LinkedList[T]" def __init__(self, data: T): self.data = data head_int: LinkedList[int] = LinkedList(1) head_int.next = LinkedList(2) head_int.next = 2
Comme vous pouvez le voir, pour les types génériques, l'inférence automatique du type de paramètre fonctionne.
Si nécessaire, un générique peut avoir un nombre quelconque de paramètres: Generic[T1, T2, T3]
.
De plus, lors de la définition de TypeVar, vous pouvez restreindre les types valides:
T2 = TypeVar("T2", int, float) class SomethingNumeric(Generic[T2]): pass x = SomethingNumeric[str]()
Cast
Parfois, l'analyseur statique de l'analyseur ne peut pas déterminer correctement le type de variable, dans ce cas, vous pouvez utiliser la fonction cast. Sa seule tâche est de montrer à l'analyseur que l'expression est d'un type particulier. Par exemple:
from typing import List, cast def find_first_str(a: List[object]) -> str: index = next(i for i, x in enumerate(a) if isinstance(x, str)) return cast(str, a[index])
Il peut également être utile pour les décorateurs:
MyCallable = TypeVar("MyCallable", bound=Callable) def logged(func: MyCallable) -> MyCallable: @wraps(func) def wrapper(*args, **kwargs): print(func.__name__, args, kwargs) return func(*args, **kwargs) return cast(MyCallable, wrapper) @logged def mysum(a: int, b: int) -> int: return a + b mysum(a=1)
Travailler avec des annotations d'exécution
Bien que l'interpréteur n'utilise pas seul les annotations, elles sont disponibles pour votre code pendant l'exécution du programme. Pour cela, un __annotations__
objet __annotations__
est __annotations__
qui contient un dictionnaire avec les annotations indiquées. Pour les fonctions, il s'agit des annotations de paramètres et du type de retour, pour un objet, des annotations de champ, de la portée globale, des variables et de leurs annotations.
def render_int(num: int) -> str: return str(num) print(render_int.annotations)
get_type_hints
est également disponible - il renvoie des annotations pour l'objet qui lui est transmis, dans de nombreuses situations, il correspond au contenu de __annotations__
, mais il existe des différences: il ajoute également des annotations des objets parents (dans l'ordre inverse de __mro__
), et permet également des déclarations préliminaires de types spécifiés en tant que chaînes.
T = TypeVar("T") class LinkedList(Generic[T]): data: T next: "LinkedList[T]" print(LinkedList.__annotations__)
Pour les types génériques, des informations sur le type lui-même et ses paramètres sont disponibles via les __origin__
et __args__
, mais cela ne fait pas partie de la norme et le comportement a déjà changé entre les versions 3.6 et 3.7