web-dev-qa-db-fra.com

Que sont les indications de type dans Python 3.5

Une des fonctionnalités mentionnées dans Python 3.5 est dite type hints.

Un exemple de type hints est mentionné dans cet article et this , tout en mentionnant également que les indications de type doivent être utilisées de manière responsable. Quelqu'un peut-il expliquer plus à ce sujet et quand il devrait être utilisé et quand pas?

199
Vaulstein

Je suggérerais de lire PEP 48 et PEP 484 et de regarder ceci présentation de Guido sur les conseils de type.

En un mot : L'indication de type est littéralement ce que signifient les mots, vous indiquez le type d'objet (s) que vous utilisez.

En raison de la nature dynamique de Python, inférer ou vérifier le type d'un objet utilisé est particulièrement difficile. Il est donc difficile pour les développeurs de comprendre exactement ce qui se passe dans le code qu'ils n'ont pas écrit et, surtout, pour les outils de vérification de type trouvés dans de nombreux IDE [PyCharm, PyDev, viennent à l'esprit], qui sont limités du fait ils n'ont aucun indicateur du type d'objets. En conséquence, ils tentent de déduire le type avec (comme mentionné dans la présentation) un taux de réussite d’environ 50%.


Pour extraire deux diapositives importantes de la présentation des conseils de type:

Pourquoi les astuces de type?

  1. Aide les vérificateurs de type: En indiquant de quel type vous souhaitez que l'objet soit le vérificateur de type peut facilement détecter si, par exemple, vous passez un objet avec un type qui n'est pas prévu.
  2. Aide à la documentation: Une troisième personne visualisant votre code saura ce qui est attendu de l'endroit où, ergo, comment l'utiliser sans l'obtenir TypeErrors.
  3. Aide les IDE à développer des outils plus précis et plus robustes: Les environnements de développement seront mieux adaptés pour suggérer des méthodes appropriées lorsque vous connaissez le type de votre objet. Vous avez probablement déjà expérimenté cela avec un certain IDE à un moment donné, en touchant le _._ et en affichant des méthodes/attributs qui ne sont pas définis pour un objet.

Pourquoi utiliser des vérificateurs de types statiques?

  • Trouvez les bugs plus tôt : Cela va de soi, je crois.
  • Plus votre projet est grand, plus vous en avez besoin : Encore une fois, cela a du sens. Les langages statiques offrent une robustesse et un contrôle qui font défaut aux langages dynamiques. Plus votre application devient grande et complexe, plus vous avez besoin de contrôle et de prévisibilité (du point de vue comportemental).
  • Les grandes équipes utilisent déjà l'analyse statique : Je suppose que cela vérifie les deux premiers points.

En guise de conclusion pour cette petite introduction : Ceci est une fonctionnalité optionnelle et, d'après ce que j'ai compris, il a été introduit pour tirer parti des avantages du typage statique.

En général vous n'avez pas à vous inquiéter et définitivement ne le faites pas besoin de l’utiliser (en particulier dans les cas où vous utilisez Python comme langage de script auxiliaire). Cela devrait être utile lors du développement de grands projets car il offre la robustesse, le contrôle et les capacités de débogage supplémentaires qui font défaut.


Indice de type avec mypy :

Afin de rendre cette réponse plus complète, je pense qu'une petite démonstration serait appropriée. J'utiliserai mypy , la bibliothèque qui a inspiré les astuces de types telles qu'elles sont présentées dans le PEP. Ceci est principalement écrit pour quiconque se heurte à cette question et se demande par où commencer.

Avant de commencer, permettez-moi de répéter ce qui suit: PEP 484 n'applique rien; il s'agit simplement de définir une direction pour les annotations de fonctions et de proposer des instructions pour comment la vérification de type peut/doit être effectuée. Vous pouvez annoter vos fonctions et indiquer autant de choses que vous le souhaitez. vos scripts seront toujours exécutés indépendamment de la présence d'annotations car Python lui-même ne les utilise pas.

Quoi qu’il en soit, comme indiqué dans le PEP, les types d'indices doivent généralement prendre trois formes:

  • Annotations de fonctions. ( PEP 3107 )
  • Fichiers de remplacement pour les modules intégrés/utilisateur.
  • Des commentaires spéciaux _# type: type_ qui complètent les deux premiers formulaires. (Voir: Que sont les annotations de variables dans Python 3.6? pour un Python 3.6 mise à jour pour _# type: type_ commentaires)

En outre, vous souhaiterez utiliser les indicateurs de type avec le nouveau module typing introduit dans _Py3.5_. Dans ce document, de nombreux ABC (supplémentaires) (classes de base abstraites) sont définis ainsi que des fonctions d'assistance et des décorateurs à utiliser dans la vérification statique. La plupart des ABCs dans _collections.abc_ sont inclus, mais sous une forme Generic afin de permettre l’abonnement (en définissant une méthode __getitem__()).

Pour les personnes intéressées par une explication plus détaillée de celles-ci, le mypy documentation est très bien écrit et contient de nombreux exemples de code illustrant/décrivant la fonctionnalité de leur vérificateur; ça vaut vraiment la peine d'être lu.

Annotations de fonction et commentaires spéciaux:

Premièrement, il est intéressant d’observer certains des comportements que nous pouvons avoir lorsque nous utilisons des commentaires spéciaux. Des commentaires spéciaux _# type: type_ peuvent être ajoutés lors des assignations de variables pour indiquer le type d'un objet s'il est impossible de le déduire directement. Les attributions simples sont généralement faciles à inférer, mais d'autres, comme les listes (en ce qui concerne leur contenu), ne le peuvent pas.

Remarque: Si nous voulons utiliser un dérivé de Containers et que nous devons spécifier le contenu de ce conteneur, nous devons utilise les types générique du module typing. Ceux-ci supportent l'indexation.

_# generic List, supports indexing.
from typing import List

# In this case, the type is easily inferred as type: int.
i = 0

# Even though the type can be inferred as of type list
# there is no way to know the contents of this list.
# By using type: List[str] we indicate we want to use a list of strings.
a = []  # type: List[str]

# Appending an int to our list
# is statically not correct.
a.append(i)

# Appending a string is fine.
a.append("i")

print(a)  # [0, 'i']
_

Si nous ajoutons ces commandes à un fichier et les exécutons avec notre interpréteur, tout fonctionnera parfaitement et print(a) affichera simplement le contenu de la liste a. Les commentaires _# type_ ont été ignorés, traités comme des commentaires simples n’ayant pas de signification sémantique supplémentaire.

En exécutant ceci avec mypy, en revanche, nous obtenons la réponse suivante:

_(Python3)jimmi@jim: mypy typeHintsCode.py
typesInline.py:14: error: Argument 1 to "append" of "list" has incompatible type "int"; expected "str"
_

Indique qu'une liste d'objets str ne peut pas contenir un int qui, statiquement, est correct. Cela peut être corrigé soit en respectant le type de a et en ajoutant uniquement des objets str, soit en modifiant le type du contenu de a pour indiquer que toute valeur est acceptable (Intuitivement exécuté avec _List[Any]_ après que Any a été importé de typing ).

Les annotations de fonction sont ajoutées sous la forme _param_name : type_ après chaque paramètre de la signature de votre fonction et un type de retour est spécifié à l'aide de la notation _-> type_ avant les deux-points de fin; toutes les annotations sont stockées dans l'attribut ___annotations___ pour cette fonction dans un dictionnaire très pratique. En utilisant un exemple trivial (qui ne nécessite pas de types supplémentaires du module typing):

_def annotated(x: int, y: str) -> bool:
    return x < y
_

L'attribut _annotated.__annotations___ a maintenant les valeurs suivantes:

_{'y': <class 'str'>, 'return': <class 'bool'>, 'x': <class 'int'>}
_

Si nous sommes un noobie complet, ou si nous connaissons les concepts _Py2.7_ et ne sommes par conséquent pas conscients de la présence de TypeError dans la comparaison de annotated, nous pouvons effectuer une autre vérification statique, détecter l'erreur et nous éviter quelques problèmes:

_(Python3)jimmi@jim: mypy typeHintsCode.py
typeFunction.py: note: In function "annotated":
typeFunction.py:2: error: Unsupported operand types for > ("str" and "int")
_

Entre autres choses, appeler la fonction avec des arguments non valides sera également attrapé:

_annotated(20, 20)

# mypy complains:
typeHintsCode.py:4: error: Argument 2 to "annotated" has incompatible type "int"; expected "str"
_

Celles-ci peuvent être étendues à n'importe quel cas d'utilisation et les erreurs détectées vont au-delà des appels et opérations de base. Les types que vous pouvez vérifier sont vraiment flexibles et je me suis contenté de donner un aperçu de son potentiel. Un aperçu du module typing, des PEP ou de la documentation mypy vous donnera une idée plus complète des fonctionnalités offertes.

Fichiers de remplacement:

Les fichiers de raccord peuvent être utilisés dans deux cas différents non mutuellement exclusifs:

  • Vous devez taper cocher un module pour lequel vous ne voulez pas modifier directement les signatures de fonction
  • Vous souhaitez écrire des modules et effectuer une vérification de type, mais souhaitez également séparer les annotations du contenu.

Les fichiers de raccord (avec une extension de _.pyi_) sont une interface annotée du module que vous créez/souhaitez utiliser. Ils contiennent les signatures des fonctions que vous voulez dactylographier avec le corps des fonctions supprimées. Pour avoir une idée de cela, étant donné un ensemble de trois fonctions aléatoires dans un module nommé _randfunc.py_:

_def message(s):
    print(s)

def alterContents(myIterable):
    return [i for i in myIterable if i % 2 == 0]

def combine(messageFunc, itFunc):
    messageFunc("Printing the Iterable")
    a = alterContents(range(1, 20))
    return set(a)
_

Nous pouvons créer un fichier de raccord _randfunc.pyi_ dans lequel nous pouvons placer certaines restrictions si nous le souhaitons. L'inconvénient est que quelqu'un qui consulte la source sans le stub n'obtiendra pas réellement cette aide d'annotation lorsqu'il tentera de comprendre ce qui est supposé être passé où.

Quoi qu'il en soit, la structure d'un fichier de raccord est assez simpliste: ajoutez toutes les définitions de fonction avec des corps vides (remplis avec pass) et fournissez les annotations en fonction de vos besoins. Ici, supposons que nous ne voulions travailler qu'avec les types int pour nos conteneurs.

_# Stub for randfucn.py
from typing import Iterable, List, Set, Callable

def message(s: str) -> None: pass

def alterContents(myIterable: Iterable[int])-> List[int]: pass

def combine(
    messageFunc: Callable[[str], Any],
    itFunc: Callable[[Iterable[int]], List[int]]
)-> Set[int]: pass
_

La fonction combine donne une indication de la raison pour laquelle vous pouvez utiliser des annotations dans un fichier différent. Elles encombrent parfois le code et réduisent la lisibilité (grand non-non pour Python). Vous pouvez bien sûr utiliser des alias de types, mais cela vous gêne parfois beaucoup (cela dit, utilisez-les judicieusement).


Cela devrait vous familiariser avec les concepts de base des astuces de types en Python. Même si le vérificateur de type utilisé a été mypy, vous devriez progressivement en voir plus apparaître d’autres, en interne dans les IDE ( PyCharm ,) et autres en tant que modules python standard. J'essaierai d'ajouter des correcteurs/packages associés supplémentaires dans la liste suivante quand et si je les trouve (ou si suggéré).

Les vérificateurs que je connais:

  • Mypy : comme décrit ici.
  • PyType : Par Google, utilise une notation différente de celle que je pense, ce qui vaut probablement le coup d'oeil.

Packages/projets associés:

  • dactylographié: officiel Python repo contenant un assortiment de fichiers de raccord pour la bibliothèque standard.

Le projet typeshed est en fait l’un des meilleurs endroits où vous pouvez rechercher comment le repérage de type peut être utilisé dans un projet de votre choix. Prenons comme exemple les ___init___ dunders de la classe Counter dans le fichier _.pyi_ correspondant:

_class Counter(Dict[_T, int], Generic[_T]):
        @overload
        def __init__(self) -> None: ...
        @overload
        def __init__(self, Mapping: Mapping[_T, int]) -> None: ...
        @overload
        def __init__(self, iterable: Iterable[_T]) -> None: ...
_

_T = TypeVar('_T') est utilisé pour définir des classes génériques . Pour la classe Counter, nous pouvons voir qu’elle ne peut prendre aucun argument dans son initialiseur, obtenir un seul Mapping de n’importe quel type en un into prendre un Iterable de n’importe quel type.


Remarque : Une chose que j'ai oublié de mentionner est que le module typing a été introduit sur une base provisoire. De PEP 411:

Un paquetage provisoire peut avoir son API modifiée avant de "passer" à un état "stable". D'une part, cet état offre au paquet l'avantage de faire officiellement partie de la distribution Python. D'autre part, l'équipe de développement principale déclare explicitement qu'aucune promesse n'est faite concernant la stabilité de l'API du package, qui pourrait changer pour la prochaine version. Bien que cela soit considéré comme un résultat improbable, de tels packages peuvent même être supprimés de la bibliothèque standard sans période de dépréciation si les préoccupations concernant leur API ou leur maintenance s'avèrent bien fondés.

Alors, prenez les choses ici avec une pincée de sel; Je doute qu'il soit supprimé ou modifié de manière significative, mais on ne peut jamais le savoir.


** Un autre sujet tout à fait valable, mais valable dans le cadre des indications de type: _PEP 526_: Syntaxe pour les annotations de variables est un effort pour remplacer les commentaires _# type_ en introduisant une nouvelle syntaxe permettant aux utilisateurs d'annoter le type de variables dans des instructions simples _varname: type_.

Voir Que sont les annotations de variables dans Python 3.6?, comme mentionné précédemment, pour une petite introduction à celles-ci.

272

Ajoutant à la réponse élaborée de Jim:

Vérifiez le module typing) - ce module prend en charge les indicateurs de type spécifiés par PEP 484 .

Par exemple, la fonction ci-dessous prend et retourne les valeurs de type str et est annotée comme suit:

def greeting(name: str) -> str:
    return 'Hello ' + name

Le module typing prend également en charge:

  1. Type aliasing .
  2. Indicateurs de type pour fonctions de rappel .
  3. Generics - Les classes de base abstraites ont été étendues pour prendre en charge la souscription afin d'indiquer les types attendus des éléments de conteneur.
  4. Types génériques définis par l'utilisateur - Une classe définie par l'utilisateur peut être définie en tant que classe générique.
  5. Tout type - Chaque type est un sous-type de Tout.
34
Ani Menon

PyCharm 5, récemment publié, prend en charge les indications de type. Dans leur article de blog à ce sujet (voir astuce de type Python 3.5 dans PyCharm 5 ), ils offrent une excellente explication de ce que les allusions de type sont et ne sont pas ainsi que plusieurs exemples et illustrations expliquant comment les utiliser dans votre code.

En outre, il est pris en charge dans Python 2.7, comme expliqué dans ce commentaire :

PyCharm prend en charge le module de typage de PyPI pour Python 2.7, Python 3.2-3.4. Pour 2.7, vous devez insérer des indicateurs de type dans les fichiers de raccord * .pyi, car des annotations de fonctions ont été ajoutées dans Python 3.0 .

22
tsvenson