2017-09-23 6 views
1

Je veux créer mon propre type paramétrés en Python pour être utilisé dans le type hinting:Comment puis-je créer mon propre type "paramétré" en Python (comme `Optional [T]`)?

class MaybeWrapped: 
    # magic goes here 

T = TypeVar('T') 

assert MaybeWrapped[T] == Union[T, Tuple[T]] 

Ne vous préoccupez pas exemple artificiel; comment puis-je l'implémenter? J'ai regardé la source pour Union et Facultatif, mais cela ressemble à un hackery assez bas que je voudrais éviter. La seule suggestion dans la documentation vient d'un example re-implementation of Mapping[KT,VT] that inherits from Generic. Mais cet exemple est plus sur la méthode __getitem__ que sur la classe elle-même.

Répondre

1

Si vous essayez simplement de créer des classes ou des fonctions génériques, jetez un oeil au documentation on mypy-lang.org about generic types - c'est assez complet, et plus détaillé que les docs de typage de bibliothèque standard.

Si vous essayez de mettre en œuvre votre exemple spécifique, il est intéressant de souligner que type aliases work with typevars - vous pouvez simplement faire:

from typing import Union, TypeVar, Tuple 

T = TypeVar('T') 

MaybeWrapped = Union[T, Tuple[T]] 

def foo(x: int) -> MaybeWrapped[str]: 
    if x % 2 == 0: 
     return "hi" 
    else: 
     return ("bye",) 

# When running mypy, the output of this line is: 
# test.py:13: error: Revealed type is 'Union[builtins.str, Tuple[builtins.str]]' 
reveal_type(foo(3)) 

Cependant, si vous essayez de construire un type générique véritablement nouveau sémantique, vous êtes très probablement hors de la chance. Vos options restantes sont:

  1. Construct une sorte de chose de classe personnalisée/métaclasse PEP 484 conforme aux dames de type peuvent comprendre et utiliser.
  2. Modifiez le vérificateur de type que vous utilisez en quelque sorte (mypy a un système expérimental "plugin", par exemple)
  3. Pétition pour modifier PEP 484 pour inclure votre nouveau type personnalisé (vous pouvez le faire en ouvrant un problème dans le typing module repo).
+0

Merci, en utilisant 'TypeVar' voici ce qui me manquait. Assez incroyable que les génériques "fonctionnent" comme ça. – shadowtalker

2

C'est exactement la méthode __getitem__ qui fait toute la magie.

C'est la méthode appelée lorsque vous souscrivez un nom avec [ et ] parenthèses. Donc, vous avez besoin d'une méthode __getitem__ dans la classe de votre classe - c'est-à-dire, sa métaclasse, qui aura comme paramètres tout ce qui est entre les parenthèses. Cette méthode est responsable de la création dynamique (ou de la récupération d'une copie mise en cache) de tout ce que vous voulez générer, et de le retourner. Je ne peux tout simplement pas imaginer comment vous voulez cela pour l'indication de type, puisque la bibliothèque de frappe semble couvrir tous les cas raisonnables (je ne peux pas penser à un exemple qu'ils ne couvrent pas déjà). Mais supposons que vous voulez une classe pour retourner une copie de lui-même, mais avec le paramètre annotée comme type_ attribut:

class MyMeta(type): 
    def __getitem__(cls, key): 
     new_cls = types.new_class(f"{cls.__name__}_{key.__name__}", (cls,), {}, lambda ns: ns.__setitem__("type", key)) 
     return new_cls 

class Base(metaclass=MyMeta): pass 

Et à essayer cela en mode interactif, on peut faire:

In [27]: Base[int] 
Out[27]: types.Base_int 
+0

Merci, même si je ne sais pas encore comment tout cela fonctionne. Pouvez-vous parler de l'exemple dans ma question? L'exemple de votre réponse semble un peu différent de ce que j'essaie de faire. – shadowtalker

+1

@jsbueno - Je ne pense pas que votre réponse fonctionne. Le code que vous avez proposé est certainement un moyen de construire quelque chose qui ressemble à un type PEP 484, mais comme ce n'est pas le cas, les vérificateurs de type PEP 484 ne comprendront pas ce que vous devez faire avec vos classes MyMeta ou Base. . – Michael0x2a