web-dev-qa-db-fra.com

Python 3.7: vérifier si l'annotation de type est "sous-classe" de générique

J'essaie de trouver un moyen fiable/cross-version (3.5+) de vérifier si une annotation de type est une "sous-classe" d'un type générique donné (c'est-à-dire extraire le type générique de l'objet d'annotation de type).

Sur Python 3.5/3.6, cela fonctionne comme une brise, comme vous vous en doutez:

>>> from typing import List

>>> isinstance(List[str], type)
True

>>> issubclass(List[str], List)
True

Alors que sur 3.7, il semble que les instances de types génériques ne sont plus des instances de type, donc il échouera:

>>> from typing import List

>>> isinstance(List[str], type)
False

>>> issubclass(List[str], List)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.7/typing.py", line 716, in __subclasscheck__
    raise TypeError("Subscripted generics cannot be used with"
TypeError: Subscripted generics cannot be used with class and instance checks

D'autres idées qui viennent à l'esprit sont la vérification du type d'instance réel, mais:

Python 3.6/3.5:

>>> type(List[str])
<class 'typing.GenericMeta'>

Python 3.7:

>>> type(List[str])
<class 'typing._GenericAlias'>

Mais cela ne donne pas vraiment d'indication supplémentaire sur le type générique réel (peut-être pas List); de plus, il est tout à fait erroné de faire le contrôle de cette façon, surtout que le _GenericAlias est devenu un type "privé" (notez le trait de soulignement).

Une autre chose que l'on pourrait vérifier est le __Origin__ argument sur le type, mais cela ne semble pas non plus être la bonne façon de le faire.

Et il diffère toujours sur 3.7:

>>> List[str].__Origin__
<class 'list'>

tandis que 3.5/3.6:

>>> List[str].__Origin__
typing.List

J'ai cherché la "bonne" façon de le faire, mais je ne l'ai pas trouvée dans la recherche Python docs/google).

Maintenant, je suppose qu'il doit y avoir une façon propre de faire cette vérification, car des outils comme mypy s'appuieraient sur elle pour faire des vérifications de type ..?

Mise à jour: à propos du cas d'utilisation

Ok en ajoutant un peu plus de contexte ici ..

Ainsi, mon cas d'utilisation pour cela utilise l'introspection sur les signatures de fonction (types d'arguments/valeurs par défaut, type de retour, docstring) pour générer automatiquement un schéma GraphQL pour eux (réduisant ainsi la quantité de passe-partout).

Je suis encore un peu déchiré quant à savoir si ce serait une bonne idée ou non.

Je l'aime du point de vue de l'utilisabilité (pas besoin d'apprendre encore une autre façon de déclarer votre signature de fonction: juste annoter vos types de la manière habituelle); voir les deux exemples de code ici pour comprendre ce que je veux dire: https://github.com/rshk/pyql

Je me demande si le support de types génériques (listes, dict, unions, ...) en utilisant des types de typing de cette façon ajoute trop de "magie noire", qui pourrait casser de manière inattendue. (Ce n'est pas un gros problème pour l'instant, mais qu'en est-il des futures Python, au-delà de 3.7? Est-ce que cela va devenir un cauchemar de maintenance?).

Bien sûr, l'alternative serait d'utiliser simplement une annotation de type personnalisée qui prend en charge une vérification plus fiable/à l'épreuve du temps, par exemple: https://github.com/rshk/pyql/blob/master/pyql/schema/ types/core.py # L337-L339

..mais à la baisse, cela obligerait les gens à se rappeler qu'ils doivent utiliser l'annotation de type personnalisé. De plus, je ne sais pas comment mypy traiterait cela (je suppose qu'il doit y avoir une déclaration quelque part pour dire que le type personnalisé est entièrement compatible avec typing.List ..? Cela semble toujours hackish).

(Je demande surtout des suggestions sur les deux approches, et surtout les avantages/inconvénients des deux alternatives que j'ai pu manquer. J'espère que cela ne devient pas "trop ​​large" pour SO ..).

18
redShadow

Tout d'abord: il n'y a pas d'API définie pour les objets de type introspectifs tels que définis par le module typing. Les outils d'indication de type sont censés traiter le code source , donc du texte, pas des objets Python au moment de l'exécution; mypy n'introspecte pas les objets List[str], il traite à la place un Abstract Syntax Tree de votre code source.

Ainsi, bien que vous puissiez toujours accéder à des attributs comme __Origin__, Vous traitez essentiellement des détails d'implémentation ( comptabilité interne =), et ces détails d'implémentation peuvent et vont changer d'une version à l'autre.

Cela dit, un contributeur principal de mypy/typing a créé le module typing_inspect pour développer une API d'introspection pour les indices de type. Le projet se documente toujours comme expérimental , et vous pouvez vous attendre à ce que cela change avec le temps jusqu'à ce qu'il ne soit plus expérimental. Il ne résoudra pas votre problème ici, car il ne prend pas en charge Python 3.5, et sa fonction get_Origin() renvoie les mêmes valeurs exactes que l'attribut __Origin__ fournit.

Avec toutes ces mises en garde, ce à quoi vous voulez accéder sur Python 3.5/Python 3.6 est l'attribut __extra__; C'est le type intégré de base utilisé pour piloter la prise en charge de issubclass()/isinstance() que la bibliothèque a implémentée à l'origine (mais supprimée depuis en 3.7):

def get_type_class(typ):
    try:
        # Python 3.5 / 3.6
        return typ.__extra__
    except AttributeError:
        # Python 3.7
        return typ.__Origin__

Cela produit <class 'list'> Dans Python 3.5 et plus, peu importe. Il utilise toujours les détails de l'implémentation interne et pourrait bien se briser dans les futures versions Python.

6
Martijn Pieters

Notez que Python 3.8 ajoute typing.get_Origin() et typing.get_args() pour prendre en charge l'introspection de base.

2
Max Gasner