Je suggère de lire le PEP 483 et PEP 484 et de regarder cette présentation de Guido sur Type Hinting.
En un mot : l' indication de type est littéralement ce que signifient les mots, vous indiquez le type des objets que vous utilisez .
En raison de la dynamique nature de Python, déduire ou vérifier le type d'un objet utilisé est particulièrement difficile. Ce fait rend difficile pour les développeurs de comprendre ce qui se passe exactement 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 que ils n'ont aucun indicateur du type des objets. En conséquence, ils ont recours à essayer de déduire le type avec (comme mentionné dans la présentation) un taux de réussite d'environ 50%.
Pour prendre deux diapositives importantes de la présentation Type Hinting:
Pourquoi saisir des indices?
- Aide les vérificateurs de type: en faisant allusion au type que vous voulez 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 attendu.
- Aide à la documentation: une troisième personne qui consulte votre code saura ce qui est attendu où, ergo, comment l'utiliser sans les obtenir
TypeErrors
.
- 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 savez de quel type est votre objet. Vous avez probablement vécu cela avec un IDE à un moment donné, en frappant le
.
et en faisant apparaître des méthodes / attributs qui ne sont pas définis pour un objet.
Pourquoi utiliser des vérificateurs de type statiques?
- Trouvez les bogues plus tôt : cela va de soi, je crois.
- Plus votre projet est grand, plus vous en avez besoin : encore une fois, c'est logique. Les langages statiques offrent une robustesse et un contrôle qui font défaut aux langages dynamiques. Plus votre application est grande et complexe, plus vous avez besoin de contrôle et de prévisibilité (d'un point de vue comportemental).
- Les grandes équipes exécutent déjà une analyse statique : je suppose que cela vérifie les deux premiers points.
En guise de note de clôture pour cette petite introduction : Ceci est une option fonctionnalité et, d'après ce que je comprends, elle a été introduite afin de profiter de certains des avantages de la frappe statique.
Vous n'avez généralement pas besoin de vous en soucier et vous n'avez certainement pas besoin de l'utiliser (en particulier dans les cas où vous utilisez Python comme langage de script auxiliaire). Il 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 nécessaires .
Tapez Hinting avec mypy :
Afin de rendre cette réponse plus complète, je pense qu'une petite démonstration conviendrait. J'utiliserai mypy
la bibliothèque qui a inspiré les conseils de type tels qu'ils sont présentés dans le PEP. Ceci est principalement écrit pour quiconque se heurte à cette question et se demande par où commencer.
Avant de le faire, 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 fonction et de proposer des directives sur la manière dont la vérification de type peut / doit être effectuée. Vous pouvez annoter vos fonctions et suggérer autant de choses que vous le souhaitez; vos scripts s'exécuteront malgré 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 fonction. ( PEP 3107 )
- Fichiers de raccord pour les modules intégrés / utilisateur.
# type: type
Commentaires spéciaux qui complètent les deux premiers formulaires. (Voir: Que sont les annotations variables dans Python 3.6? Pour une mise à jour Python 3.6 pour les # type: type
commentaires)
De plus, vous souhaiterez utiliser des indications de type conjointement avec le nouveau typing
module introduit dans Py3.5
. Dans ce document, de nombreux ABC (classes de base abstraites) (supplémentaires) sont définis ainsi que des fonctions d'assistance et des décorateurs à utiliser dans la vérification statique. La plupart ABCs
en collections.abc
sont inclus , mais dans une Generic
forme afin de permettre l' abonnement (en définissant un__getitem__()
méthode).
Pour toute personne intéressée par une explication plus approfondie de ceux-ci, le mypy documentation
est très bien écrit et possède de nombreux exemples de code démontrant / décrivant la fonctionnalité de leur vérificateur; cela vaut vraiment la peine d'être lu.
Annotations de fonction et commentaires spéciaux:
Tout d'abord, il est intéressant d'observer certains des comportements que nous pouvons obtenir lors de l'utilisation de commentaires spéciaux. Des # type: type
commentaires spéciaux peuvent être ajoutés lors des affectations de variables pour indiquer le type d'un objet s'il ne peut pas être directement déduit. Les affectations simples sont généralement faciles à déduire, 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 devons spécifier le contenu de ce conteneur, nous devons utiliser les types génériques du typing
module. Ceux-ci prennent en charge 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ète, tout fonctionne très bien et print(a)
imprime simplement le contenu de la liste a
. Les # type
commentaires ont été rejetés, traités comme des commentaires simples qui n'ont aucune signification sémantique supplémentaire .
En exécutant cela avec mypy
, d'autre part, 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"
Indiquant qu'une liste d' str
objets ne peut pas contenir un int
qui, statiquement parlant, est sain. Cela peut être résolu soit en respectant le type a
et en ajoutant uniquement des str
objets, soit en changeant le type du contenu de a
pour indiquer que toute valeur est acceptable (effectuée de manière intuitive avec List[Any]
after Any
a été importée depuis typing
).
Les annotations de fonction sont ajoutées sous la forme param_name : type
après chaque paramètre de votre signature de fonction et un type de retour est spécifié à l'aide de la -> type
notation avant le signe deux-points de la fonction de fin; toutes les annotations sont stockées dans l' __annotations__
attribut de cette fonction dans un dictionnaire pratique. En utilisant un exemple trivial (qui ne nécessite pas de types supplémentaires du typing
module):
def annotated(x: int, y: str) -> bool:
return x < y
L' annotated.__annotations__
attribut 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 Py2.7
concepts et que nous ne sommes donc pas conscients de ce qui se TypeError
cache dans la comparaison de annotated
, nous pouvons effectuer une autre vérification statique, détecter l'erreur et nous éviter des ennuis:
(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, l'appel de la fonction avec des arguments invalides sera également intercepté:
annotated(20, 20)
# mypy complains:
typeHintsCode.py:4: error: Argument 2 to "annotated" has incompatible type "int"; expected "str"
Ceux-ci peuvent être étendus à pratiquement tous les cas d'utilisation et les erreurs détectées s'étendent plus loin que les appels et les opérations de base. Les types que vous pouvez vérifier sont vraiment flexibles et je viens de donner un petit aperçu de son potentiel. Un coup d'oeil dans le typing
module, les PEP ou les mypy
docs vous donnera une idée plus complète des capacités offertes.
Fichiers de raccord:
Les fichiers de raccord peuvent être utilisés dans deux cas différents qui ne s'excluent pas mutuellement:
- Vous devez taper check un module pour lequel vous ne souhaitez pas modifier directement les signatures de fonction
- Vous souhaitez écrire des modules et effectuer une vérification de type, mais vous souhaitez en outre 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 souhaitez vérifier 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 stub randfunc.pyi
, dans lequel nous pouvons placer des restrictions si nous le souhaitons. L'inconvénient est que quelqu'un qui regarde la source sans le talon n'obtiendra pas vraiment cette aide d'annotation lorsqu'il essaiera de comprendre ce qui est censé ê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 ( pass
remplis) et fournissez les annotations en fonction de vos besoins. Ici, supposons que nous ne voulons travailler qu'avec des int
types 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 combine
fonction donne une indication de la raison pour laquelle vous voudrez peut-être utiliser des annotations dans un fichier différent, elles encombrent parfois le code et réduisent la lisibilité (gros no-no pour Python). Vous pouvez bien sûr utiliser des alias de type, mais cela crée parfois plus de confusion que cela n'aide (alors utilisez-les judicieusement).
Cela devrait vous familiariser avec les concepts de base des conseils de type en Python. Même si le vérificateur de type utilisé a été,
mypy
vous devriez progressivement commencer à en voir davantage, certains en interne dans les IDE ( PyCharm ) et d'autres en tant que modules python standard. J'essaierai d'ajouter des vérificateurs supplémentaires / packages connexes dans la liste suivante quand et si je les trouve (ou si suggéré).
Dames que je connais :
- Mypy : comme décrit ici.
- PyType : par Google, utilise une notation différente de celle que je recueille, vaut probablement le coup d'œil.
Forfaits / projets connexes :
- typeshed: Repo officiel Python hébergeant un assortiment de fichiers stub pour la bibliothèque standard.
Le typeshed
projet est en fait l'un des meilleurs endroits où vous pouvez regarder pour voir comment les indications de type peuvent être utilisées dans votre propre projet. Prenons comme exemple les __init__
dunders de la Counter
classe dans le .pyi
fichier 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 les classes génériques . Pour la Counter
classe, nous pouvons voir qu'elle ne peut prendre aucun argument dans son initialiseur, en obtenir un simple Mapping
de n'importe quel type vers un int
ou prendre un Iterable
de n'importe quel type.
Remarque : Une chose que j'ai oublié de mentionner est que le typing
module a été introduit à titre provisoire . Depuis PEP 411 :
Un package provisoire peut avoir son API modifiée avant de "passer" à un état "stable". D'une part, cet état fournit au package les avantages de faire officiellement partie de la distribution Python. En revanche, l'équipe de développement principale déclare explicitement qu'aucune promesse n'est faite en ce qui concerne 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, ces 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 fondées.
Prenez donc 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, mais valide dans le cadre des indices de type PEP 526
:: La syntaxe des annotations de variable est un effort pour remplacer les # type
commentaires en introduisant une nouvelle syntaxe qui permet aux utilisateurs d'annoter le type de variables dans des varname: type
instructions simples .
Voir Que sont les annotations variables dans Python 3.6? , comme mentionné précédemment, pour une petite introduction sur ces derniers.