[PYTHON] [Français] PEP 0484 - Conseils de type

Cet article est une traduction du type hint PEP 0484 introduit dans Python 3.5.

Nous n'avons pas eu suffisamment de temps pour le traduire, donc si vous avez une mauvaise traduction ou une traduction plus appropriée, veuillez nous en informer dans une demande de modification.

PEP 0484 - Indication de type

PEP: 484
Titre: Indice de saisie
Auteur: Guido van Rossum <guido at python.org>, Jukka Lehtosalo <jukka.lehtosalo at iki.fi>, Łukasz Langa <lukasz at langa.pl>
BDFL-Délégation: Mark Shannon
Discussions-To: Python-Dev <python-dev at python.org>
statut: Autorisation
Type: Processus de normalisation
Date de création: 29 septembre 2014
Historique des publications: 16 janvier 2015, 20 mars 2015, 17 avril 2015, 20 mai 2015, 22 mai 2015
résolution: https://mail.python.org/pipermail/python-dev/2015-May/140104.html

Présentation

PEP 3107 a introduit la syntaxe des annotations de fonction, mais leur sémantique était délibérément indéfinie. Aujourd'hui, il existe de nombreuses utilisations tierces pour l'analyse de type statique, et la communauté peut bénéficier d'un vocabulaire standardisé et d'outils de base dans les bibliothèques standard.

Ce PEP introduit des conventions pour les situations où les modules provisoires et les annotations ne sont pas disponibles pour fournir des définitions et des outils normalisés.

Ce PEP ne bloque toujours pas explicitement les autres utilisations des annotations. De plus, veuillez noter que se conformer à cette spécification n'exige (ou n'interdit) aucun traitement spécial des annotations. Cela apporte simplement une meilleure coordination, comme PEP 333 est allé au framework web.

Par exemple, la fonction simple suivante déclare ses types d'argument et de retour avec des annotations.

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

Bien que ces annotations puissent être référencées en tant qu'attributs normaux __annotations__ au moment de l'exécution, elles n'effectuent pas de vérification de type au moment de l'exécution. Au lieu de cela, cette proposition suppose l'existence d'un vérificateur hors ligne distinct. Les utilisateurs peuvent volontairement inspecter le code source en utilisant un tel vérificateur de type. Fondamentalement, ces contrôleurs de type agissent comme des linters très puissants. (Bien sûr, des vérificateurs similaires pourraient être utilisés pour appliquer les optimisations Design By Contracts et JIT pour les utilisateurs individuels au moment de l'exécution, mais ces outils sont toujours à un niveau pratique. ne pas.)

Cette suggestion est fortement inspirée de mypy [mypy]. Par exemple, le type de "séquence de type entier" est décrit comme "Sequence [int]". Il n'est pas nécessaire d'ajouter une nouvelle syntaxe au langage en utilisant des crochets. L'exemple présenté ici utilise le type personnalisé «Sequence» importé du module de typage pure-Python. Des notations telles que Sequence [int]fonctionnent à l'exécution en implémentantgetitem ()` dans la métaclasse (mais sa signature est principalement pour les vérificateurs hors ligne).

Le système de types prend en charge les types d'union, les types génériques et les types spéciaux qui sont cohérents (c'est-à-dire mutuellement assignables) avec tous les types, ʻAny`. Cette dernière fonction est héritée de l'idée de typage progressif. Le typage progressif et l'ensemble du système de type sont décrits dans PEP 483.

D'autres méthodes que nous avons empruntées, ou d'autres méthodes de comparaison ou de contraste, sont résumées dans PEP 482.

Raison d'être et objectif

PEP 3107 a été ajouté pour prendre en charge les annotations arbitraires dans le cadre de la définition de la fonction. L'annotation n'avait pas de signification particulière à ce moment-là, mais elle avait toujours le but potentiel de l'utiliser pour des indices de type [[gvr-artima]](https: //www.python. org / dev / peps / pep-0484 / # gvr-artima). La vérification de type est mentionnée comme le premier cas d'utilisation dans PEP 3107.

Ce PEP vise à fournir une syntaxe normalisée pour les annotations de type. Il ouvre les possibilités du code Python pour une analyse et une refactorisation statiques faciles, une vérification de type potentielle au moment de l'exécution et (éventuellement dans un certain contexte) la génération de code avec des informations de type.

Le plus important de ces objectifs est l'analyse statique. Cela inclut la prise en charge des vérificateurs hors ligne comme mypy et la fourniture de la notation standard utilisée par l'EDI pour l'achèvement du code et la refactorisation.

Non intentionnel

Le module de typage proposé contient des composants pour la vérification à l'exécution, en particulier la fonction `` get_type_hints () '', mais pour implémenter certaines fonctionnalités de vérification à l'exécution (par exemple les décorateurs et Vous devrez développer un package tiers (en utilisant des métaclasses). L'utilisation d'indices de type pour optimiser les performances reste un défi pour les lecteurs de ce PEP.

Soulignons également ce qui suit. ** Python est toujours un langage typé dynamiquement. Les auteurs Python ne veulent pas exiger d'indices de type (même comme conventions). ** **

Annotation Semantics

Toute fonction non annotée doit être traitée comme ayant un type aussi générique que possible par tous les vérificateurs de type ou doit être ignorée. Les fonctions avec le décorateur @ no_type_check ou le commentaire # type: ignore doivent être traitées comme n'ayant pas d'annotations.

Il est recommandé, mais non obligatoire, que la fonction soit vérifiée pour avoir tous les arguments et renvoyer des annotations. L'annotation par défaut pour les arguments de fonction vérifiés et les types de retour est ʻAny. L'exception est que les premiers arguments des méthodes d'instance et de classe n'ont pas besoin d'être annotés. Il est supposé que la méthode d'instance a le type de la classe à laquelle appartient la méthode d'instance et que la méthode de classe a le type d'objet type correspondant à l'objet de classe auquel appartient la méthode de classe. Par exemple, le premier argument d'une méthode d'instance de classe ʻA est implicitement de type ʻA`. Les méthodes de classe ne peuvent pas représenter le type exact du premier argument dans la notation de type disponible.

(Notez que le type de retour de __init__ doit être annoté comme-> None. La raison est subtile. Si __init__ est une annotation du type de retour-> None Une méthode __init__ non annotée sans arguments devrait-elle être vérifiée de type si elle est considérée comme ayant? init` indique qu'il devrait avoir une annotation de retour, ce qui rend son comportement par défaut similaire à celui des autres méthodes.)

Le vérificateur de type doit vérifier si le contenu de la fonction en cours de vérification est cohérent avec l'annotation spécifiée. Les annotations sont également utilisées pour vérifier la validité des appels qui apparaissent dans d'autres fonctions vérifiées.

Les vérificateurs de type doivent essayer de deviner autant d'informations que nécessaire. La condition minimale est de gérer les décorateurs intégrés @property, @ staticmethod et @ classmethod.

Syntaxe de la définition de type

La syntaxe tire parti des annotations de style PEP 3107 ainsi que de nombreuses extensions décrites dans les sections suivantes. Sa forme de base consiste à utiliser des indices de type en remplissant les emplacements d'annotation de fonction avec des classes.

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

Ceci déclare que le type attendu de l'argument name est str. De même, le type de retour attendu est «str».

Les expressions dont le type est un sous-type du type d'un argument particulier sont également acceptables comme cet argument.

Indice de type autorisé

Les indices de type peuvent être des classes intégrées (y compris celles définies dans des bibliothèques standard ou des modules d'extension tiers), des classes de base abstraites, des types définis dans le module types et des classes définies par l'utilisateur (bibliothèques standard ou modules tiers). (Y compris ceux définis dans).

Bien que les annotations soient généralement le meilleur format pour les indications de type, il peut être plus approprié de représenter les indications de type dans des commentaires spéciaux ou dans un fichier stub séparé. (Voir l'exemple ci-dessous.)

L'annotation doit être une expression valide qui évalue sans exception lorsque la fonction est définie (mais voir ci-dessous pour les références directes).

Les annotations doivent rester simples, sinon les outils d'analyse statique risquent de ne pas pouvoir interpréter leurs valeurs. Par exemple, il est peu probable que les types calculés dynamiquement soient compréhensibles. (C'est intentionnellement une exigence quelque peu vague. À la suite de la discussion, certaines spécifications supplémentaires ou d'exception pourraient être ajoutées aux futures versions de ce PEP.)

En plus de ce qui précède, des constructeurs spéciaux définis ci-dessous sont disponibles: Toutes les classes de base abstraites exposées dans None, ʻAny, ʻUnion, Tuple, Callable, typing Et des alternatives pour les classes concrètes (par exemple "Sequence" et "Dict"), les variables de type et les alias de type.

Tous les noms nouvellement introduits (tels que ʻAny et ʻUnion) utilisés pour supporter les fonctionnalités décrites dans les sections suivantes sont fournis par le module typing.

Aucune utilisation

Lorsqu'elle est utilisée dans les indicateurs de type, l'expression «Aucun» est considérée comme équivalente à «type (Aucun)».

Tapez l'alias

Les alias de type sont simplement définis en affectant des variables.

Url = str

def retry(url: Url, retry_count: int) -> None:
    ...

Le point à noter ici est qu'il est recommandé de mettre en majuscule le début des caractères dans l'alias. C'est parce que les alias représentent des types définis par l'utilisateur, qui sont généralement écrits de cette façon (comme les classes définies par l'utilisateur).

Les alias de type peuvent être aussi complexes que les indices de type dans les annotations. Tout ce qui est acceptable comme indice de type est acceptable comme alias de type.

from typing import TypeVar, Iterable, Tuple

T = TypeVar('T', int, float, complex)
Vector = Iterable[Tuple[T, T]]

def inproduct(v: Vector) -> T:
    return sum(x*y for x, y in v)

Cela équivaut au code suivant.

from typing import TypeVar, Iterable, Tuple

T = TypeVar('T', int, float, complex)

def inproduct(v: Iterable[Tuple[T, T]]) -> T:
    return sum(x*y for x, y in v)

Objets appelables (https://www.python.org/dev/peps/pep-0484/#id17)

Les cadres qui reçoivent des fonctions de rappel avec une signature particulière peuvent utiliser Callable [[Arg1Type, Arg2Type], ReturnType] pour faire des indications de type. Voici un exemple.

from typing import Callable

def feeder(get_next_item: Callable[[], str]) -> None:
    # Body

def async_query(on_success: Callable[[int], None],
                on_error: Callable[[int, Exception], None]) -> None:
    # Body

Vous pouvez déclarer le type de retour d'un objet appelable sans spécifier de signature d'appel en remplaçant la liste d'arguments par une ellipse littérale (trois points).

def partial(func: Callable[..., str], *args) -> Callable[..., str]:
    # Body

Notez qu'il n'y a pas de crochets avant ou après les points de suspension. Dans ce cas, il n'y a aucune restriction sur les arguments de la fonction de rappel (et les arguments de mot clé sont acceptables).

Il n'y a actuellement aucun mécanisme pour spécifier des arguments de mot-clé pour Callable, car l'utilisation d'arguments de mot-clé dans les fonctions de rappel n'est pas reconnue comme un cas d'utilisation courant. De même, il n'existe aucun mécanisme pour spécifier un type particulier d'argument de longueur variable dans une signature de rappel.

Puisque typing.Callable a deux rôles en remplacement de collections.abc.Callable, ʻisinstance (x, typing.Callable)remplace ʻisinstance (x, collections.abc.Callable). Il est mis en œuvre en retardant. Cependant, ʻis instance (x, typing.Callable [...]) `n'est pas supporté.

Génériques

Les informations de type sur les objets d'un conteneur ne peuvent pas être induites de manière statique de la manière habituelle. En conséquence, la classe de base abstraite a été étendue pour prendre en charge les indices de tableau pour indiquer le type d'élément conteneur attendu. Voici un exemple.

from typing import Mapping, Set

def notify_by_email(employees: Set[Employee], overrides: Mapping[str, str]) -> None:
    ...

Les génériques sont paramétrés à l'aide d'une nouvelle usine appelée «TypeVar» dans le module «typage». Voici un exemple.

from typing import Sequence, TypeVar

T = TypeVar('T')      # Declare type variable

def first(l: Sequence[T]) -> T:   # Generic function
    return l[0]

Dans ce cas, le contrat est que la valeur de retour est cohérente avec les éléments contenus dans la collection.

L'expression TypeVar () doit toujours être affectée directement à une variable (ne doit pas être utilisée dans le cadre d'une expression plus grande). L'argument passé à «TypeVar ()» doit être une chaîne équivalente au nom de la variable à laquelle il est affecté. Ne redéfinissez pas les variables de type.

TypeVar prend en charge les types paramétrés qui contraignent un ensemble particulier de types possibles. Par exemple, vous pouvez définir une variable de type qui ne gère que «str» et «octets». Par défaut, les variables de type gèrent tous les types possibles. Ceci est un exemple de contrainte de variable de type.

from typing import TypeVar

AnyStr = TypeVar('AnyStr', str, bytes)

def concat(x: AnyStr, y: AnyStr) -> AnyStr:
    return x + y

La fonction concat peut être appelée avec deux arguments de type str ou deux arguments de type bytes, mais avec un mélange d'arguments de type str et de type bytes. Il ne peut pas être appelé.

Il doit y avoir au moins deux contraintes, le cas échéant. Vous ne pouvez pas spécifier une seule contrainte.

Les sous-types du type contraint par la variable de type doivent être traités comme leurs types de base respectifs représentés explicitement dans le contexte de la variable de type. Prenons cet exemple.

class MyStr(str):
    ...

x = concat(MyStr('apple'), MyStr('pie'))

Cet appel est valide, mais sa variable de type ʻAnyStr sera définie sur strau lieu deMyStr`. Le type déduit comme valeur de retour affectée à «x» est en fait «str».

De plus, ʻAny` est une valeur valide pour toutes les variables de type. Considérer ce qui suit:

def count_truthy(elements: List[Any]) -> int:
    return sum(1 for elem in elements if element)

Cela équivaut à supprimer la notation générique et à dire simplement «éléments: Liste».

Type générique défini par l'utilisateur

Vous pouvez utiliser la classe de base Generic pour définir une classe définie par l'utilisateur comme type générique. Voici un exemple.

from typing import TypeVar, Generic

T = TypeVar('T')

class LoggedVar(Generic[T]):
    def __init__(self, value: T, name: str, logger: Logger) -> None:
        self.name = name
        self.logger = logger
        self.value = value

    def set(self, new: T) -> None:
        self.log('Set ' + repr(self.value))
        self.value = new

    def get(self) -> T:
        self.log('Get ' + repr(self.value))
        return self.value

    def log(self, message: str) -> None:
        self.logger.info('{}: {}'.format(self.name message))

Generic [T] comme classe de base définit une classe LoggedVar qui prend un paramètre de type T. Il active également T comme type dans la classe.

La classe de base Generic utilise une métaclasse qui définit __getitem__ pour queLoggedVar [t]soit valide comme type.

from typing import Iterable

def zero_all_vars(vars: Iterable[LoggedVar[int]]) -> None:
    for var in vars:
        var.set(0)

Les types génériques prennent n'importe quel nombre de variables de type et les contraignent. Le code suivant est valide.

from typing import TypeVar, Generic
...

T = TypeVar('T')
S = TypeVar('S')

class Pair(Generic[T, S]):
    ...

Les variables de type individuelles passées à «Générique» doivent être séparées. Cela invalide le code suivant.

from typing import TypeVar, Generic
...

T = TypeVar('T')

class Pair(Generic[T, T]):   # INVALID
    ...

L'héritage multiple peut être utilisé pour «Générique».

from typing import TypeVar, Generic, Sized

T = TypeVar('T')

class LinkedList(Sized, Generic[T]):
    ...

Si vous sous-classez une classe générique sans spécifier de paramètre de type, il est supposé que vous avez spécifié ʻAnypour chaque argument positionnel. Dans l'exemple suivant,MyIterable n'est pas de type générique, mais hérite implicitement de ʻIterable [Any].

from typing import Iterable

class MyIterable(Iterable): # Same as Iterable[Any]
    ...

Les métaclasses de type générique ne sont pas prises en charge.

Instanciation de classe générique et élimination de type

Les types génériques tels que «List» et «Sequence» ne peuvent pas être instanciés. Cependant, les classes définies par l'utilisateur dérivées de ces types génériques peuvent être instanciées. Prenons une classe Node qui hérite de Generic [T].

from typing import TypeVar, Generic

T = TypeVar('T')

class Node(Generic[T]):
    ...

Il existe maintenant deux façons d'instancier cette classe. Le type que le vérificateur de type déduit dépend du format que vous utilisez. La première méthode consiste à transmettre explicitement la valeur du paramètre type. Cela remplace toute inférence de type effectuée par le vérificateur de type.

x = Node[T]() # The type inferred for x is Node[T].
y = Node[int]() # The type inferred for y is Node[int].

Si le type n'est pas explicitement spécifié, le vérificateur de type est libre de déduire. Considérez le code suivant.

x = Node()

Le type inféré sera Node [Any]. Il n'y a pas assez de contexte pour déduire un type plus précis. Sinon, le vérificateur de type peut ne pas accepter cette ligne et exiger une annotation explicite comme suit:

x = Node() # type: Node[int] # Inferred type is Node[int].

Les vérificateurs de type avec des inférences de type plus puissantes examinent comment «x» est utilisé dans le fichier, et même si aucune annotation de type explicite n'est trouvée, l'inférence de type est plus précise, comme «Node [int]». Je vais essayer. Mais il est peut-être impossible de faire fonctionner ce type d'inférence de type dans tous les cas. Les programmes Python peuvent être trop dynamiques.

Ce PEP ne fournit pas de détails sur le fonctionnement de l'inférence de type. Nous permettons à différents outils d'essayer différentes approches. Les versions futures pourraient définir des règles plus claires.

Le type n'est pas conservé au moment de l'exécution. La classe de «x» est simplement «Node» dans tous les cas. Ce comportement est appelé «élimination de type». C'est une pratique commune aux langages avec des génériques (par exemple Java et TypeScript).

Tout type générique comme classe de base

Generic [T] n'est valide que comme classe de base. Ce n'est pas strictement un type. Cependant, les types génériques définis par l'utilisateur dans LinkedList [T] dans l'exemple de code ci-dessus, les types génériques intégrés et les classes de base abstraites telles que List [T] et ʻIterable [T] `peuvent également être des types. Elle est également valable comme classe de base. Par exemple, vous pouvez définir une sous-classe de «Dict» avec des arguments de type spécialisés.

from typing import Dict, List, Optional

class Node:
    ...

class SymbolTable(Dict[str, List[Node]]):
    def push(self, name: str, node: Node) -> None:
        self.setdefault(name, []).append(node)

    def pop(self, name: str) -> Node:
        return self[name].pop()

    def lookup(self, name: str) -> Optional[Node]:
        nodes = self.get(name)
        if nodes:
            return nodes[-1]
        return None

«SymbolTable» est une sous-classe de «dict» et un sous-type de «Dict [str, List [Node]]».

Si la classe de base générique a une variable de type comme argument de type, cela rend la classe définie générique. Par exemple, vous pouvez définir une classe générique LinkedList qui est un objet conteneur répétable.

from typing import TypeVar, Iterable, Container

T = TypeVar('T')

class LinkedList(Iterable[T], Container[T]):
    ...

Maintenant LinkedList [int] est un type valide. Notez que vous pouvez utiliser T plusieurs fois dans la liste des classes de base si vous n'utilisez pas T plusieurs fois dans Generic [...].

Considérez également l'exemple suivant.

from typing import TypeVar, Mapping

T = TypeVar('T')

class MyDict(Mapping[str, T]):
    ...

Dans ce cas, MyDict prend un seul paramètre «T».

Type générique abstrait

La métaclasse utilisée par Generic est une sous-classe de ʻabc.ABCMeta`. Les classes génériques deviennent des classes de base abstraites en incluant des méthodes ou des propriétés abstraites. De plus, les classes génériques peuvent avoir plusieurs classes de base abstraites comme classes de base sans conflits de métaclasses.

Variable de type avec borne supérieure

Les variables de type peuvent être dépassées en utilisant bound = type. Cela signifie que le type réel qui remplace la variable de type (explicitement ou implicitement) doit être une sous-classe du type de limite. Un exemple courant est une définition d'un type comparable qui fonctionne suffisamment bien pour détecter les erreurs les plus courantes.

from typing import TypeVar

class Comparable(metaclass=ABCMeta):
    @abstractmethod
    def __lt__(self, other: Any) -> bool:
        ...
    ... # __gt__ etc. as well

CT = TypeVar('CT', bound=Comparable)

def min(x: CT, y: CT) -> CT:
    if x < y:
        return x
    else:
        return y

min(1, 2) # ok, return type int
min('x', 'y') # ok, return type str

(Notez que ce n'est pas toujours idéal. Par exemple, min ('x', 1) entraînera une erreur d'exécution, mais le vérificateur de type déduit simplement Comparable comme type de retour. Malheureusement, nous devons introduire un concept plus puissant et complexe, le polymorphisme lié par F, pour résoudre ce problème. À l'avenir, nous pourrons repenser ce concept.)

Les limites supérieures ne peuvent pas être combinées avec des contraintes de type (comme ʻAnyStr` utilisé dans l'exemple précédent). Les contraintes de type garantissent que le type inféré est \ _exactement \ _ correspondant à l'un des types contraints. La limite supérieure, par contre, exige uniquement que le type réel soit une sous-classe du type de limite.

Co-modification et anti-modification

Considérez la classe ʻEmployeeet sa sous-classeManager. Supposons que vous ayez maintenant une fonction avec des arguments annotés avec List [Employee]. Doit-il être autorisé à appeler cette fonction avec une variable de type «List [Manager]» comme argument? Beaucoup répondraient «oui, bien sûr» sans même penser à ses conséquences logiques. Cependant, les vérificateurs de type doivent rejeter ces appels à moins d'en savoir plus sur la fonction. La fonction peut ajouter une instance ʻEmpoyee à sa liste. Il enfreint le type de variable sur l'appelant.

Il s'avère que ces arguments ont un comportement \ _contravariantly). La réponse intuitive (correcte si cette fonction ne change pas l'argument!) Demande un argument qui se comporte \ _ de manière covariante. Une longue introduction à ces concepts peut être trouvée sur Wikipedia [wiki-variance]. Ici, expliquons brièvement comment contrôler le comportement du vérificateur de type.

Par défaut, les variables de type sont supposées être \ _invariant \ _ (invariant). Cela signifie que les arguments des arguments annotés, tels que le type List [Employee], doivent correspondre exactement à l'annotation de type. Les sous-classes et superclasses de paramètres de type (ʻEmployee` dans cet exemple) ne sont pas autorisées.

Pour déclarer un type de conteneur qui accepte les vérifications de type covariant, utilisez covariant = True pour déclarer la variable de type. Passez contravariant = True si un comportement contravariant est requis (rare). Vous pouvez réussir au plus l'un d'entre eux.

Un exemple typique est la définition d'une classe de conteneur non réinscriptible (ou en lecture seule).

from typing import TypeVar, Generic, Iterable, Iterator

T = TypeVar('T', covariant=True)

class ImmutableList(Generic[T]):
    def __init__(self, items: Iterable[T]) -> None:
        ...
    def __iter__(self) -> Iterator[T]:
        ...
    ...

class Employee:
    ...

class Manager(Employee):
    ...

def dump_employees(emps: ImmutableList[Employee]) -> None:
    for emp in emps:
        ...

mgrs = ImmutableList([Manager()])  # type: ImmutableList[Manager]
dump_employees(mgrs)  # OK

Toutes les classes de collection en lecture seule dans le module typing (par exemple, Mapping et Sequence) sont déclarées avec des variables de type covariant. Les classes de collection de variables (par exemple, "MutableMapping" et "MutableSequence") sont définies à l'aide de variables de type immuables régulières. Un exemple de variable de type contravariant est le type «Générateur». Le type d'argument de send () est contravariant (voir ci-dessous).

Remarque: la variation affecte les paramètres de type générique. Cela n'affecte pas les paramètres normaux. Par exemple, l'exemple suivant est très bien.

from typing import TypeVar

class Employee:
    ...

class Manager(Employee):
    ...

E = TypeVar('E', bound=Employee)  # Invariant

def dump_employee(e: E) -> None:
    ...

dump_employee(Manager())  # OK

Hiérarchie numérique

PEP 3141 définit une hiérarchie numérique de Python. Le module numbers de la bibliothèque standard implémente les classes de base abstraites correspondantes ( Number, Complex, Real, Rational, ʻIntegral`). Ces classes de base abstraites ont quelques défis, mais les classes numériques concrètes intégrées «complex», «float» et «int »sont utilisées partout (en particulier les deux dernières :-).

Au lieu de demander à l'utilisateur d'écrire des «numéros d'importation» et d'utiliser quelque chose comme «numbers.Float», ce PEP suggère un simple raccourci qui a presque le même effet: annoter si un argument a le type «float». Les arguments de type ʻintsont également autorisés une fois terminés. De même, les arguments de type «float» ou «int »sont autorisés pour les arguments annotés avec le type« complex ». Il ne peut pas gérer les classes qui implémentent la classe de base abstraite correspondante ou la classefractions.Fraction`, mais je pense que de tels cas d'utilisation sont très rares.

Type d'octet

Il existe trois classes intégrées pour les tableaux d'octets: bytes, bytearray et memoryview (les classes fournies par le module ʻarray` ne sont pas comptées). Bien sûr, «bytes» et «bytearray» partagent beaucoup de comportement (mais pas tous, par exemple «bytearray» peut être changé).

Une classe de base commune appelée ByteString est définie dans collections.abc, et le type correspondant existe également dans typing, mais parce qu'il y a généralement des fonctions qui acceptent (n'importe quel) type d'octet. Il serait fastidieux d'avoir à écrire «typing.ByteString» partout. Par conséquent, en tant que raccourci similaire à la classe numérique intégrée, un type «bytearray» ou «memoryview» est également autorisé lorsque l'argument est annoté comme ayant un type «bytes». (Répétez, il y a des situations où cela ne fonctionne pas, mais je pense que c'est rare dans la pratique.)

Reportez-vous à

Lorsqu'un indice de type contient un nom qui n'a pas encore été défini, la définition peut être représentée sous la forme d'une chaîne littérale. Le nom représenté par le littéral de chaîne sera résolu ultérieurement.

Une situation courante où cela se produit est lors de la définition d'une classe de conteneur. Pour les classes de conteneur, la classe que vous définissez apparaît dans les signatures de certaines méthodes. Par exemple, le code suivant (les premières lignes d'une implémentation d'arbre binaire simple) ne fonctionne pas.

class Tree:
    def __init__(self, left: Tree, right: Tree):
        self.left = left
        self.right = right

Pour y correspondre, écrivez comme suit.

class Tree:
    def __init__(self, left: 'Tree', right: 'Tree'):
        self.left = left
        self.right = right

La chaîne littérale doit contenir une expression Python valide (c'est-à-dire que compile (allumé, '', 'eval') devient un objet de code valide) lorsque le module est complètement chargé. Il doit pouvoir évaluer sans erreur. Les espaces de noms locaux et globaux lors de son évaluation doivent être les mêmes que l'espace de noms dans lequel les arguments par défaut pour la même fonction sont évalués.

De plus, l'expression doit être analysable en tant qu'indicateur de type valide. Par exemple, il est contraint par les règles mentionnées ci-dessus dans la section Conseils sur les types acceptables (https://www.python.org/dev/peps/pep-0484/#acceptable-type-hints).

Il est permis d'utiliser des littéraux de chaîne comme _part _ d'indices de type. Par exemple

class Tree:
    ...
    def leaves(self) -> List['Tree']:
        ...

Un cas d'utilisation courant pour les références directes est, par exemple, lorsque le modèle Django est requis pour les signatures. Chaque modèle se trouve généralement dans un fichier séparé et possède des méthodes qui acceptent des arguments avec des types liés à l'autre modèle. En raison de la solution d'importation circulaire en Python, il n'est souvent pas possible d'importer directement tous les modèles dont vous avez besoin.

# File models/a.py
from models.b import B
class A(Model):
    def foo(self, b: B):
        ...

# File models/b.py
from models.a import A
class B(Model):
    def bar(self, a: A):
        ...

# File main.py
from models.a import A
from models.b import B

En supposant que le principal est importé en premier, il échoue avec une ImportError sur la ligne from models.a import A dans models / b.py. Cela est dû au fait que b.py a été importé de models / a.py avant la définition de la classe A. La solution est de passer à l'importation de modules uniquement et de référencer le modèle au format \ _module \ _. \ _ Class \ _.

# File models/a.py
from models import b
class A(Model):
    def foo(self, b: 'b.B'):
        ...

# File models/b.py
from models import a
class B(Model):
    def bar(self, a: 'a.A'):
        ...

# File main.py
from models.a import A
from models.b import B

Types d'union

Comme il est courant de recevoir un ensemble limité de types attendus pour un seul argument, une fabrique spéciale appelée «Union» est fournie. Voici un exemple.

from typing import Union

def handle_employees(e: Union[Employee, Sequence[Employee]]) -> None:
    if isinstance(e, Employee):
        e = [e]
    ...

Le type représenté par ʻUnion [T1, T2, ...] est le résultat de la vérification ʻissubclass pour T1 et son sous-type arbitraire, T2 et son sous-type arbitraire, etc. Ce sera "Vrai".

Un cas d'utilisation courant du type à somme directe est le type optionnel. Par défaut, "Aucun" n'est valide pour tous les types, sauf si vous spécifiez la valeur par défaut "Aucun" lors de la définition de la fonction. Voici un exemple.

def handle_employee(e: Union[Employee, None]) -> None:
    ...

Vous pouvez écrire ʻOptional [T1] comme une représentation simplifiée de ʻUnion [T1, None]. Par exemple, le code précédent est équivalent au code suivant.

from typing import Optional

def handle_employee(e: Optional[Employee]) -> None:
    ...

De plus, lorsque la valeur par défaut est «Aucun», elle est automatiquement considérée comme un type facultatif. Par exemple

def handle_employee(e: Employee = None):
    ...

Cela équivaut au code suivant.

def handle_employee(e: Optional[Employee] = None) -> None:
    ...

Type ʻAny`

ʻAny est un type spécial. Tous les types sont des sous-types de ʻAny. La même chose est vraie pour l '«objet» intégré. Cependant, pour les vérificateurs statiques, ils sont complètement différents.

Lorsque le type d'une valeur est ʻobject, le vérificateur de type refusera presque toutes les opérations sur cette valeur. Et l'affectation de cette valeur à une variable d'un type plus spécialisé (ou l'utilisation de cette valeur comme valeur de retour) entraînera une erreur de type. En revanche, lorsqu'une valeur est de type ʻAny, le vérificateur de type autorise désormais toutes les opérations sur cette valeur et affecte une valeur de type ʻAny` à une variable de type plus contraint (ou Vous pouvez utiliser cette valeur comme valeur de retour).

Vérifier la version et la plate-forme

Le vérificateur de type est nécessaire pour pouvoir effectuer des vérifications simples de version et de plate-forme. Voici un exemple.

import sys

if sys.version_info[0] >= 3:
    # Python 3 specific definitions
else:
    # Python 2 specific definitions

if sys.platform == 'win32':
    # Windows specific definitions
else:
    # Posix specific definitions

Ne vous attendez pas à ce que le vérificateur gère du code obscurci comme " ". Join (reverse (sys.platform) ==" xunil ".

Valeur d'argument par défaut

Dans les stubs, il peut être utile de pouvoir déclarer des arguments avec des valeurs par défaut sans spécifier les valeurs par défaut réelles. Voici un exemple.

def foo(x: AnyStr, y: AnyStr = ...) -> AnyStr:
    ...

Quelle devrait être la valeur par défaut? Soit " ", b" " ou None ne satisfait pas la contrainte de type (en fait None _ change son type en ʻOptional [AnyStr]` _ ).

Dans ces cas, vous pouvez spécifier la valeur par défaut comme des points de suspension littéraux. En d'autres termes, l'exemple ci-dessus représente exactement ce que vous voulez écrire.

Compatibilité avec l'utilisation d'autres annotations de fonction

Il existe de nombreux cas d'utilisation d'annotations de fonction existants ou potentiels, qui sont incompatibles avec les indicateurs de type. De telles choses peuvent confondre les contrôleurs statiques. Cependant, l'annotation de type hint ne fait rien au moment de l'exécution (elle ne fait rien d'autre qu'évaluer l'expression d'annotation et conserver l'annotation dans l'attribut __annotations__ de l'objet fonction). Cela ne rend pas le programme malveillant. Le vérificateur de type imprimera simplement le mauvais avertissement ou erreur.

Pour éviter d'appliquer des conseils de type à des parties de votre programme, utilisez certaines des méthodes décrites ci-dessous.

Voir les sections suivantes pour plus de détails.

En fin de compte, il peut être judicieux de basculer l'interface dépendante de l'annotation vers un autre mécanisme (par exemple, un décorateur) pour une compatibilité maximale avec la vérification hors ligne. Cela dit, une telle pression n'existe pas dans Python 3.5. Voir également la longue discussion sur les alternatives rejetées (https://www.python.org/dev/peps/pep-0484/#rejected-alternatives) ci-dessous.

Tapez un commentaire

Ce PEP n'ajoute pas de syntaxe de première classe pour marquer explicitement une variable comme un type particulier. Les commentaires dans les formats suivants peuvent être utilisés pour faciliter l'inférence de type dans les cas complexes.

x = []   # type: List[Employee]
x, y, z = [], [], []  # type: List[int], List[int], List[str]
x, y, z = [], [], []  # type: (List[int], List[int], List[str])
x = [
   1,
   2,
]  # type: List[int]

Le commentaire de type doit être sur la dernière ligne de l'instruction contenant la déclaration de variable. Les commentaires de type peuvent également être placés immédiatement après les deux points dans les instructions «with» et «for».

Exemples de commentaires de type pour les instructions «with» et «for».

with frobnicate() as foo:  # type: int
    # Here foo is an int
    ...

for x, y in points:  # type: float, float
    # Here x and y are floats
    ...

Dans les stubs, il peut être utile de déclarer l'existence d'une variable sans spécifier de valeur initiale. Cela peut être fait en utilisant les points de suspension littéraux.

from typing import IO

stream = ...  # type: IO[str]

Pour le code non stub, il existe des cas spéciaux similaires.

from typing import IO

stream = None # type: IO[str]

Le vérificateur de type ne doit pas considérer ce code comme une erreur (même si la valeur «Aucun» ne correspond pas au type spécifié) et doit changer le type inféré en «Optionnel [...]» Non (malgré la règle faisant cela pour l'argument annoté et la valeur par défaut «Aucun»). L'hypothèse ici est que l'autre code sera responsable de l'attribution du type de valeur approprié à cette variable. Et vous pouvez supposer que partout où vous utilisez cette variable aura le type spécifié.

Le commentaire # type: ignore doit être placé sur la ligne qui fait référence à l'erreur.

import http.client
errors = {
    'not_found': http.client.NOT_FOUND  # type: ignore
}

# type: ignore Si un commentaire existe sur une seule ligne, désactive toutes les vérifications de type de la ligne avec le commentaire au reste du fichier.

Si les conseils de type s'avèrent utiles en général, la syntaxe de la variable de type peut être fournie dans les futures versions de Python.

Diffuser

Dans certains cas, le vérificateur de type peut avoir besoin de différents types d'indices: Le programmeur peut savoir qu'une expression est un type plus contraint que le vérificateur de type ne peut en déduire. 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))
    # We only get here if there's at least one string in a
    return cast(str, a[index])

Certains vérificateurs de type peuvent ne pas être capables de déduire que le type de ʻa [index] ʻest str, mais l'inférer comme ʻobject ou ʻAny. Cependant, le programmeur sait que la chaîne est correcte (si le code y arrive). cast (t, x) indique au vérificateur de type qu'il est sûr que le type de x est t. Au moment de l'exécution, la distribution renvoie toujours l'expression inchangée. Il ne vérifie pas son type et ne convertit pas sa valeur.

Les moulages sont différents des commentaires de type (voir la section précédente). Lors de l'utilisation de commentaires de type, le vérificateur de type doit toujours vérifier que le type inféré est cohérent avec son type déclaré. Lors de l'utilisation de cast, le vérificateur de type doit faire confiance au programmeur sans discernement. En outre, les casts sont utilisés dans les expressions, tandis que les commentaires de type sont appliqués dans les affectations.

Fichier stub

Un fichier stub est un fichier qui contient des indications de type et n'est utilisé que par le vérificateur de type, pas au moment de l'exécution. Il existe différents cas d'utilisation des fichiers stub.

Les fichiers stub utilisent la même syntaxe que les modules Python classiques. Il existe une fonctionnalité du module typing qui ne peut être utilisée qu'avec des fichiers stub. C'est le décorateur «@ overload» décrit dans les sections suivantes.

Le vérificateur de type ne doit vérifier que la signature de la fonction dans le fichier stub. Il est recommandé que le corps de la fonction du fichier stub soit uniquement les points de suspension littéraux (...).

Le vérificateur de type doit être en mesure de définir le chemin de recherche pour le fichier stub. Si le fichier stub est trouvé, le vérificateur de type ne doit pas charger le module "réel" correspondant.

Bien que le fichier stub soit un module Python syntaxiquement valide, il utilise l'extension .pyi afin que le fichier stub puisse être conservé dans le même répertoire que le module réel correspondant. En séparant le fichier stub, vous pouvez renforcer l'impression du comportement attendu du fichier stub selon lequel il ne fait rien au moment de l'exécution.

Autres remarques sur les fichiers stub.

Surcharge de fonction

Le décorateur @ overload vous permet d'écrire des fonctions qui supportent différentes combinaisons de types d'arguments. Ce modèle est fréquemment utilisé pour les modules intégrés et les types intégrés. Par exemple, la méthode __getitem__ () de type bytes serait écrite comme suit:

from typing import overload

class bytes:
    ...
    @overload
    def __getitem__(self, i: int) -> int:
        ...
    @overload
    def __getitem__(self, s: slice) -> bytes:
        ...

Ce code est plus strict que ce qui est obtenu en utilisant le type de somme directe (qui ne peut pas représenter la relation entre l'argument et le type de retour).

from typing import Union

class bytes:
    ...
    def __getitem__(self, a: Union[int, slice]) -> Union[int, bytes]:
        ...

Un autre exemple où @ overload est utile est le type de la fonctionmap ()intégrée. La fonction map () prend différents arguments selon le type d'objet appelable.

from typing import Callable, Iterable, Iterator, Tuple, TypeVar, overload

T1 = TypeVar('T1')
T2 = TypeVar('T2')
S = TypeVar('S')

@overload
def map(func: Callable[[T1], S], iter1: Iterable[T1]) -> Iterator[S]:
    ...
@overload
def map(func: Callable[[T1, T2], S],
        iter1: Iterable[T1], iter2: Iterable[T2]) -> Iterator[S]:
    ...
# ... and we could add more items to support more than two iterables

Notez également que vous pouvez facilement ajouter des éléments pour prendre en charge map (None, ...).

@overload
def map(func: None, iter1: Iterable[T1]) -> Iterable[T1]:
    ...

@overload
def map(func: None,
        iter1: Iterable[T1],
        iter2: Iterable[T2]) -> Iterable[Tuple[T1, T2]]:
    ...

Le décorateur @ overload ne peut être utilisé qu'avec des fichiers stub. Vous pouvez utiliser cette syntaxe pour fournir une implémentation de plusieurs distributions, mais cette implémentation nécessiterait sys._getframe () pour attirer l'attention de tout le monde. Il est difficile de concevoir et de mettre en œuvre un mécanisme de répartition multiple plus efficace. C'est pourquoi les tentatives passées ont été abandonnées et functools.singledispatch () a été favorisé. (Voir PEP 443, en particulier dans la section «Approches alternatives».) Concevoir plusieurs dépêches pour répondre aux besoins futurs Vous pourriez trouver, mais je ne veux pas que cette conception soit contrainte par la syntaxe de surcharge définie pour les indices de type de fichier stub. Pour le moment, utiliser le décorateur @ overload ou appeler ʻoverload ()directement entraînera unRuntimeError`.

Au lieu d'utiliser le décorateur @ overload, le type TypeVar avec des contraintes est généralement utilisé. Par exemple, les définitions de «concat1» et «concat2» dans ce fichier stub sont équivalentes.

from typing import TypeVar

AnyStr = TypeVar('AnyStr', str, bytes)

def concat1(x: AnyStr, y: AnyStr) -> AnyStr:
    ...

@overload
def concat2(x: str, y: str) -> str:
    ...

@overload
def concat2(x: bytes, y: bytes) -> bytes:
    ...

Les fonctions comme map et bytes .__ getitem__ mentionnées ci-dessus ne peuvent pas être représentées avec précision à l'aide de variables de type. Cependant, contrairement à «@ overload», les variables de type peuvent également être utilisées en dehors du fichier stub. Puisque @ overload est une condition spéciale qui ne peut être utilisée que pour les stubs, il est recommandé de ne l'utiliser que lorsque la variable de type n'est pas suffisante.

Une autre différence importante entre l'utilisation de variables de type comme ʻAnyStret@ overloadest que la première peut également être utilisée pour définir des contraintes de paramètre de type pour les classes génériques. Par exemple, il définit une contrainte pour le paramètre de type de classe génériquetyping.IO (seuls ʻIO [str], ʻIO [bytes] et ʻIO [Any] sont valides).

class IO(Generic[AnyStr]):
    ...

Emplacement de stockage et distribution des fichiers stub

Le moyen le plus simple de stocker et de distribuer des fichiers stub est de les placer dans le même répertoire avec le module Python. Cela facilite la recherche des programmeurs et des outils. Cependant, les responsables de paquets sont également libres de ne pas ajouter d'indices de type à leurs paquets. Par conséquent, les stubs tiers qui peuvent être installés à partir de PyPI avec la commande pip sont également pris en charge. Dans ce cas, trois problèmes doivent être pris en compte: la dénomination, la version et le chemin d'installation.

Ce PEP ne fournit pas de recommandations pour les schémas de dénomination pour les packages de fichiers stub tiers. Nous espérons que la trouvabilité sera basée sur la popularité du package. Par exemple, le package Django.

Les stubs tiers doivent être versionnés à l'aide de la version la plus basse d'un package source compatible. Exemple: FooPackage a les versions 1.0, 1.1, 1.2, 1.3, 2.0, 2.1, 2.2. Il y a des changements d'API dans les versions 1.1, 2.0 et 2.2. Le mainteneur de paquet de fichiers stub est libre de publier des stubs pour toutes les versions, mais les utilisateurs finaux ont besoin d'au moins 1.0, 1.1, 2.0 et 2.2 pour effectuer la vérification de type pour toutes les versions. L'utilisateur sait que la version avec le _low ou le même_stub le plus proche de la version du package est compatible. Dans l'exemple précédent, l'utilisateur choisirait la version stub 1.1 pour FooPackage 1.3.

Si l'utilisateur décide d'utiliser le "dernier" du paquet source disponible, et si le stub est mis à jour fréquemment, alors en général il fonctionnera bien avec le "dernier" fichier de stub. Faites attention.

Les packages de stub tiers peuvent utiliser n'importe quel emplacement pour le stockage de stub. Le vérificateur de type doit utiliser PYTHONPATH pour rechercher les fichiers stub. Le répertoire de secours par défaut qui est toujours vérifié est shared / typehints / python3.5 / (ou 3.6, par exemple). Étant donné qu'un seul package est installé pour une version spécifique de Python dans chaque environnement, aucun autre paramètre de version n'est défini dans ce répertoire. L'auteur du package de fichiers stub peut utiliser le code suivant dans setup.py.

...
data_files=[
    (
        'shared/typehints/python{}.{}'.format(*sys.version_info[:2]),
        pathlib.Path(SRC_PATH).glob('**/*.pyi'),
    ),
],
...

Dépôt typé

Il existe un référentiel partagé appelé [typhed] avec une collection de stubs utiles. Veuillez noter que les talons de certains packages ne peuvent pas être inclus ici sans le consentement explicite du propriétaire du package. D'autres politiques concernant les stubs collectés ici seront déterminées après discussion dans python-dev et seront rapportées dans le README du référentiel typé.

Exception

Nous ne suggérons pas une syntaxe qui indique explicitement l'exception qui se produira. Jusqu'à présent, le seul cas d'utilisation connu de cette fonctionnalité concerne la documentation. Et dans ce cas, il est recommandé de mettre ces informations dans la docstring.

module de saisie

Un espace de noms unifié est nécessaire pour étendre l'utilisation du typage statique aux anciennes versions ainsi qu'à Python 3.5. À cette fin, nous allons introduire un nouveau module appelé typing dans la bibliothèque standard.

Ce module contient les composants de base pour la construction de types (par exemple ʻAny), les types qui représentent des variantes génériques de collections intégrées (par exemple, List`), et les types qui représentent les classes de base abstraites des collections génériques. Définissez (par exemple "Séquence") et incluez d'autres définitions utiles.

Composants de base:

Version générique de la collection intégrée:

Remarque: «Dict», «List», «Set» et «FrozenSet» sont principalement utiles pour annoter la valeur de retour. Le type de collection abstraite défini ci-dessous est plus approprié pour l'argument. Par exemple, Mapping, Sequence ou ʻAbstractSet`.

Version générique de la classe de base abstraite du conteneur (et non-conteneur):

Plusieurs types à usage unique sont définis pour inspecter une seule méthode spéciale (similaire à Hashable et Sized):

Définition pratique:

Types disponibles dans le sous-module typing.io:

Types disponibles dans le sous-module typing.re:

Alternative rejetée

Divers contre-arguments ont été avancés au cours de la discussion de ce premier projet de PEP, et plusieurs alternatives ont été proposées. Nous en discuterons ici et expliquerons pourquoi nous les avons rejetés.

J'ai repris certains des principaux contre-arguments.

Quelles parenthèses utilisez-vous pour les paramètres de type générique?

La plupart des gens sont familiers avec les crochets angulaires (par exemple List <int>) qui représentent des paramètres de type générique dans des langages tels que C ++, Java, C # et Swift. Le problème est qu'il est vraiment difficile d'analyser les crochets angulaires. Cela est particulièrement vrai pour les analyseurs naïfs comme Python. Dans la plupart des langues, il est courant de gérer l'ambiguïté en autorisant uniquement les crochets angulaires dans des positions syntaxiques spéciales et en n'autorisant aucune expression. (Il utilise également une puissante technologie d'analyse qui peut revenir en arrière sur n'importe quelle partie de l'accord.)

Mais en Python, je veux que l'expression de type et les autres expressions soient (syntaxiquement) identiques. Cela vous permet d'affecter des variables, par exemple pour créer des alias de type. Considérez cette expression de type simple.

List<int>

Du point de vue de l'analyseur Python, cette expression commence avec les quatre mêmes jetons (NAME, LESS, NAME, GREATER) que la comparaison chaînée.

a < b > c  # I.e., (a < b) and (b > c)

Vous pouvez également créer un exemple qui peut être analysé des deux manières.

a < b > [ c ]

En supposant que la langue a des crochets angulaires, ce code peut être interprété de deux manières:

(a<b>)[c]      # I.e., (a<b>).__getitem__(c)
a < b > ([c])  # I.e., (a < b) and (b > [c])

Il est certain que des règles peuvent être élaborées pour lever l'ambiguïté de ces cas. Mais pour de nombreux utilisateurs, les règles sont arbitraires et complexes. Cela nécessite également des modifications radicales de l'analyseur CPython (et de tous les autres analyseurs pour Python). Il convient de noter que l'analyseur actuel de Python est intentionnellement "stupide". Cela signifie qu'une grammaire concise est facile à deviner pour l'utilisateur.

Pour toutes ces raisons, des crochets (par exemple List [int]) ont été choisis (et ont toujours été un favori) pour la syntaxe des paramètres de type générique. Cela peut être implémenté en définissant la méthode __getitem__ () dans la métaclasse et ne nécessite aucune nouvelle syntaxe. Cette option fonctionne avec toutes les versions récentes de Python (à partir de Python 2.2). Et Python n'est pas le seul à choisir cette syntaxe. Scala utilise également des crochets pour les classes génériques.

Qu'en est-il des utilisations actuelles des annotations?

L'un des arguments souligne que PEP 3107 prend explicitement en charge l'utilisation d'expressions arbitraires dans les annotations de fonction. On pense que la nouvelle proposition est incompatible avec PEP 3107.

La réponse à cela est que, tout d'abord, la proposition actuelle ne provoque pas d'incompatibilité directe. Par conséquent, les programmes qui utilisent des annotations dans Python 3.4 fonctionneront correctement dans Python 3.5 sans aucun inconvénient.

En fin de compte, nous nous attendons à ce que les indices de type soient la seule utilisation des annotations. Cependant, cela nécessite une discussion supplémentaire et une mise hors service après la publication du module de saisie précoce avec Python 3.5. Le PEP actuel sera dans un état provisoire jusqu'à la sortie de Python 3.6 (voir [PEP 411](voir https://www.python.org/dev/peps/pep-0411)). Le plan le plus rapide possible consiste à introduire une période de dépréciation initiale pour les annotations d'indication non de type dans Python 3.6, une période de mise hors service complète dans la version 3.7 et d'autoriser uniquement les déclarations d'indication de type à des fins d'annotation dans la version 3.8. Vous devez donner aux auteurs de packages qui utilisent des annotations suffisamment de temps pour concevoir d'autres approches. Même si les conseils de type réussissent rapidement.

Un autre résultat possible est que l'indicateur de type devient finalement la signification par défaut de l'annotation, mais conserve toujours l'option de désactiver l'indicateur de type. Pour cela, la proposition actuelle définit un décorateur appelé @ no_type_check qui remplace l'interprétation par défaut des annotations en tant qu'indices de type dans une classe ou une fonction. Nous définissons également un méta-décorateur appelé @ no_type_check_decorator, qui décore le décorateur (!). Les annotations de toute fonction ou classe décorée avec un décorateur créé avec ce méta-décorateur seront désormais ignorées par le vérificateur de type.

Il y a aussi un commentaire «# type: ignore». Et le vérificateur de type statique doit prendre en charge une option de configuration qui désactive la vérification de type pour le package sélectionné.

Malgré toutes ces options, il existe d'innombrables suggestions pour permettre aux indices de type et à d'autres formats d'annotations de coexister pour des arguments individuels. Une suggestion était que si l'annotation d'un argument était un littéral de dictionnaire, alors chaque clé représenterait une forme d'annotation différente, et si cette clé était `` type '', elle serait utilisée pour un indice de type. Le problème avec cette idée et ses variantes est que la notation est très bruyante et difficile à lire. En outre, dans la plupart des cas des bibliothèques existantes qui utilisent des annotations, le besoin de combiner des indices de type avec ces bibliothèques sera faible. Par conséquent, une technique concise qui désactive sélectivement les indices de type semble suffisante.

Problème de référence avant

Si l'indicateur de type doit inclure une référence directe, la proposition actuelle est clairement une solution de contournement. Python doit avoir tous les noms définis lorsqu'ils sont utilisés. Mis à part les importations circulaires, c'est rarement un problème: «utilisé» signifie ici «trouvé au moment de l'exécution». La plupart des références «en avant» conviennent tant que vous vous assurez que le nom est défini avant que la fonction qui l'utilise soit appelée.

Le problème avec les indices de type est que l'annotation est évaluée lorsque la fonction est définie (via PEP 3107, ainsi que les arguments par défaut). Tout nom utilisé dans l'annotation doit être prédéfini au moment où la fonction est définie. Un scénario courant est une définition de classe avec des méthodes qui nécessitent une référence à la classe elle-même pour annoter cette classe. (Plus généralement, cela se produit également dans les classes réciproques.) C'est naturel pour les types de conteneurs. Par exemple

class Node:
    """Binary tree node."""

    def __init__(self, left: Node, right: Node):
        self.left = left
        self.right = right

Comme je l'ai déjà écrit, cela ne fonctionne pas. Cela est dû à la propriété de Python selon laquelle les noms de classe ne sont définis que lorsque tout le corps de la classe est exécuté. Notre solution est de permettre l'utilisation de chaînes littérales dans les annotations (ce qui n'est pas particulièrement sophistiqué, mais cela fait du bon travail). Cependant, dans la plupart des cas, vous n'avez pas besoin de l'utiliser. La plupart des indicateurs de type utilisation supposent qu'ils font référence à des types intégrés ou à des types définis dans d'autres modules.

Une contre-proposition a tenté de modifier sa sémantique afin que les indicateurs de type ne soient jamais évalués à l'exécution (après tout, la vérification de type est effectuée hors ligne, les indicateurs de type doivent donc être évalués lors de l'exécution. Je me demande s'il y en a). Ceci, bien sûr, entre en conflit avec des problèmes de compatibilité descendante. L'interpréteur Python ne sait pas vraiment si une annotation particulière peut être un indice de type ou autre chose.

Il peut y avoir un compromis qui permet à l'importation __future__ de convertir toutes les annotations _ d'un module donné en littéraux de chaîne comme suit:

from __future__ import annotations

class ImSet:
    def add(self, a: ImSet) -> List[ImSet]:
        ...

assert ImSet.add.__annotations__ == {'a': 'ImSet', 'return': 'List[ImSet]'}

Une telle déclaration d'importation «future» peut être proposée dans un autre PEP.

Double deux points

Plusieurs personnes ont été créatives et mises au défi d'inventer une solution à ce problème. Par exemple, il a été suggéré d'utiliser un double deux-points (::) pour les indicateurs de type pour résoudre deux problèmes en même temps: la désambiguïsation entre les autres annotations et les indications de type, et au moment de l'exécution. C'est changer le sens pour que l'évaluation ne se produise pas. Cependant, certaines choses n'étaient pas adaptées à cette idée.

Autres formes de nouvelle syntaxe

Plusieurs autres syntaxes alternatives ont été proposées. Par exemple, l'introduction du mot réservé «where» [roberge] et la clause «requirements» inspirée de Cobra. Cependant, il a le même problème qu'un double deux-points qui ne fonctionne pas dans les versions antérieures de Python 3. Il en va de même pour les nouvelles importations «future».

Autres règles de compatibilité descendante

Comprend des idées sous proposition.

Ou il est suggéré d'attendre simplement la prochaine version. Mais quel problème résout-il? C'est juste un report.

Processus de développement PEP

Un brouillon de ce PEP peut être trouvé sur [github]. Il existe également un suivi des problèmes [issues] où de nombreuses discussions techniques ont lieu.

Les brouillons sur GitHub sont mis à jour régulièrement. Le référentiel PEPS officiel [peps] est (généralement) mis à jour uniquement lorsqu'un nouveau brouillon est posté sur python-dev.

Merci

Ce document n'aurait pas été achevé sans les précieux commentaires, encouragements et conseils de Jim Baker, Jeremy Siek, Michael Matson Vitousek, Andrey Vlasovskikh, Radomir Dopieralski, Peter Ludemann et le commissaire du BDFL Mark Shannon.

Il est influencé par les bibliothèques, les frameworks et les langages existants mentionnés dans PEP 482. Merci aux auteurs dans l'ordre alphabétique: Stefan Behnel, William Edwards, Greg Ewing, Larry Hastings, Anders Hejlsberg, Alok Menghrajani, Travis E. Oliphant, Joe Pamer, Raoul-Gabriel Urma et Julien Verlaguet

Références

- -
[mypy] http://mypy-lang.org
[gvr-artima] (1,2) http://www.artima.com/weblogs/viewpost.jsp?thread=85551
[wiki-variance] http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)
[typeshed] https://github.com/JukkaL/typeshed/
[pyflakes] https://github.com/pyflakes/pyflakes/
[pylint] http://www.pylint.org
[roberge] http://aroberge.blogspot.com/2015/01/type-hinting-in-python-focus-on.html
[github] https://github.com/ambv/typehinting
[issues] https://github.com/ambv/typehinting/issues
[peps] https://hg.python.org/peps/file/tip/pep-0484.txt

Droits d'auteur

Ce document est situé dans le domaine public.

Source: https://hg.python.org/peps/file/tip/pep-0484.txt

Recommended Posts

[Français] PEP 0484 - Conseils de type
Essayez les astuces de saisie
[Français] Type statique Python, incroyable mypy!
Entraine toi! !! Introduction au type Python (conseils de type)
J'ai lu PEP 613 (alias de type explicite)
Augmentez la visibilité de la source avec des conseils de type
Traduction japonaise: PEP 20 - Le Zen de Python