Quels sont les indices de type dans Python 3.5?

250

L'une des fonctionnalités les plus discutées de Python 3.5 est les indications de type .

Un exemple d' indices de type est mentionné dans cet article et celui-ci tout en mentionnant également d'utiliser les indices de type de manière responsable. Quelqu'un peut-il expliquer davantage à leur sujet et quand ils doivent être utilisés et quand non?

Vaulstein
la source
4
Vous devriez jeter un œil au PEP 484 qui est lié à partir du changelog officiel .
Stefan
1
@AvinashRaj: Une bonne discussion sur les versions se déroule ici
Vaulstein
1
Il est dommage que le cas d'utilisation de l'API C soit complètement ignoré par ce PEP 484, en particulier les indices de type pour Cython et Numba.
denfromufa
2
Étroitement liés: que sont les annotations variables dans Python 3.6? .
Dimitris Fasarakis Hilliard

Réponses:

343

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?

  1. 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.
  2. 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.
  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 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 mypyla 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: typeCommentaires 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: typecommentaires)

De plus, vous souhaiterez utiliser des indications de type conjointement avec le nouveau typingmodule 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 ABCsen collections.abcsont inclus , mais dans une Genericforme 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 documentationest 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: typecommentaires 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 Containerset devons spécifier le contenu de ce conteneur, nous devons utiliser les types génériques du typingmodule. 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 # typecommentaires 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' strobjets ne peut pas contenir un intqui, statiquement parlant, est sain. Cela peut être résolu soit en respectant le type aet en ajoutant uniquement des strobjets, soit en changeant le type du contenu de apour indiquer que toute valeur est acceptable (effectuée de manière intuitive avec List[Any]after Anya été importée depuis typing).

Les annotations de fonction sont ajoutées sous la forme param_name : typeaprès chaque paramètre de votre signature de fonction et un type de retour est spécifié à l'aide de la -> typenotation 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 typingmodule):

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.7concepts et que nous ne sommes donc pas conscients de ce qui se TypeErrorcache 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 typingmodule, les PEP ou les mypydocs 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 ( passremplis) et fournissez les annotations en fonction de vos besoins. Ici, supposons que nous ne voulons travailler qu'avec des inttypes 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 combinefonction 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é, mypyvous 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 typeshedprojet 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 Counterclasse dans le .pyifichier 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 les classes génériques . Pour la Counterclasse, nous pouvons voir qu'elle ne peut prendre aucun argument dans son initialiseur, en obtenir un simple Mappingde n'importe quel type vers un int ou prendre un Iterablede n'importe quel type.


Remarque : Une chose que j'ai oublié de mentionner est que le typingmodule 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 # typecommentaires en introduisant une nouvelle syntaxe qui permet aux utilisateurs d'annoter le type de variables dans des varname: typeinstructions simples .

Voir Que sont les annotations variables dans Python 3.6? , comme mentionné précédemment, pour une petite introduction sur ces derniers.

Dimitris Fasarakis Hilliard
la source
3
"En raison de la nature hautement dynamique de Python, déduire ou vérifier le type d'un objet utilisé est particulièrement difficile." Vous faites référence à la vérification statique, non?
bsam
53

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

Vérifiez le typingmodule - ce module prend en charge les indications de type comme spécifié par PEP 484 .

Par exemple, la fonction ci-dessous prend et renvoie des valeurs de type stret est annotée comme suit:

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

Le typingmodule prend également en charge:

  1. Alias ​​de type .
  2. Tapez conseil pour les fonctions de rappel .
  3. Génériques - Les classes de base abstraites ont été étendues pour prendre en charge l'abonnement afin de désigner les types attendus pour les é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 comme une classe générique.
  5. Tout type - Chaque type est un sous-type de Tout.
Ani Menon
la source
26

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

De plus, 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 la version 2.7, vous devez mettre des indications de type dans les fichiers stub * .pyi depuis que les annotations de fonction ont été ajoutées dans Python 3.0 .

tsvenson
la source
0

L'indice de type est un ajout récent à un langage dynamique où les gens ont juré pendant des décennies des conventions de dénomination aussi simples que le hongrois (étiquette d'objet avec la première lettre b = booléen, c = caractère, d = dictionnaire, i = entier, l = liste, n = numérique , s = string, t = tuple) n'étaient pas nécessaires, trop encombrants, mais ont maintenant décidé que, oh attendez ... il est beaucoup trop difficile d'utiliser le langage (type ()) pour reconnaître les objets, et nos IDE fantaisistes besoin d'aide pour tout ce qui est compliqué, et que les valeurs d'objet attribuées dynamiquement les rendent complètement inutiles de toute façon, alors qu'une simple convention de dénomination aurait pu tout résoudre, pour n'importe quel développeur, d'un simple coup d'œil.

Noah F. SanTsorvutz
la source
Pour être franc, cela ressemble plus à une diatribe qu'à une réponse.
Dimitris Fasarakis Hilliard
-1

Les indices de type sont pour la maintenabilité et ne sont pas interprétés par Python. Dans le code ci-dessous, la ligne def add(self, ic:int)ne génère pas d'erreur jusqu'à la return...ligne suivante :

class C1:
    def __init__(self):
        self.idn = 1
    def add(self, ic: int):
        return self.idn + ic
    
c1 = C1()
c1.add(2)

c1.add(c1)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "<input>", line 5, in add
TypeError: unsupported operand type(s) for +: 'int' and 'C1'
 
Leon Chang
la source