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?
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:
TypeErrors
..
_ et en affichant des méthodes/attributs qui ne sont pas définis pour un objet.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.
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:
# 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.
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.
Les fichiers de raccord peuvent être utilisés dans deux cas différents non mutuellement exclusifs:
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:
Packages/projets associés:
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: ...
_
Où _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 int
o 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.
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:
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 .