2017-09-01 3 views
3

Disons que j'ai deux classes Base et Child avec une méthode usine dans Base. La méthode factory appelle une autre méthode de classe qui peut être surchargée par les classes enfants de Base.Indice de type Python 3 pour une méthode usine sur une classe de base renvoyant une instance de classe enfant

class Base(object): 
    @classmethod 
    def create(cls, *args: Tuple) -> 'Base': 
     value = cls._prepare(*args) 
     return cls(value) 

    @classmethod 
    def _prepare(cls, *args: Tuple) -> Any: 
     return args[0] if args else None 

    def __init__(self, value: Any) -> None: 
     self.value = value 


class Child(Base): 
    @classmethod 
    def _prepare(cls, *args: Tuple) -> Any: 
     return args[1] if len(args) > 1 else None 

    def method_not_present_on_base(self) -> None: 
     pass 

Est-il possible d'annoter Base.create de sorte qu'un vérificateur de type statique pourrait en déduire que Base.create() a renvoyé une instance de Base et Child.create() a renvoyé une instance de Child, de sorte que l'exemple suivant passerait l'analyse statique?

base = Base.create(1) 
child = Child.create(2, 3) 
child.method_not_present_on_base() 

Dans l'exemple ci-dessus un vérificateur de type statique se plaindraient à juste titre que le method_not_present_on_base est, bien, pas présent sur la classe Base.


Je pensais à tourner Base dans une classe générique et ayant les classes d'enfants définissent elles-mêmes comme des arguments de type, à savoir amener le CRTP à Python.

T = TypeVar('T') 

class Base(Generic[T]): 
    @classmethod 
    def create(cls, *args: Tuple) -> T: ... 

class Child(Base['Child']): ... 

Mais cela se sent plutôt unpythonic avec CRTP venant de C++ et tout ...

Répondre

2

Il est en effet possible: la fonction est appelée TypeVar with Generic Self (bien que ce soit un peu trompeur car nous utilisons cela pour méthode de classe dans ce cas). Je crois qu'il se comporte à peu près de manière équivalente à la technique "CRTP" à laquelle vous êtes lié (bien que je ne sois pas un expert C++, donc je ne peux pas le dire avec certitude).

Dans tous les cas, vous déclarer vos classes de base et de l'enfant comme ceci:

from typing import TypeVar, Type, Tuple 

T = TypeVar('T', bound='Base') 

class Base: 
    @classmethod 
    def create(cls: Type[T], *args: Tuple[Any]) -> T: ... 

class Child(Base): 
    @classmethod 
    def create(cls, *args: Tuple[Any]) -> 'Child': ... 

Notez que:

  1. On n'a pas besoin de faire générique la classe elle-même puisque nous avons seulement besoin une fonction générique
  2. La définition de TypeVar lié à 'Base' est strictement facultative, mais c'est probablement une bonne idée: de cette façon, les appelants de votre classe/sous-classe de base pourront au moins appeler les méthodes définies dans la classe de base même si vous faites Je ne sais pas exactement quelle sous-classe vous avez affaire.
  3. Nous pouvons omettre l'annotation sur cls pour la définition de l'enfant.
+0

Génial, merci! Malheureusement, PyCharm traite maintenant les méthodes privées sur 'cls' comme accédant à un membre privé sur un autre objet, mais mypy le traite comme prévu. – PoByBolek

+0

C'est vraiment cool! Merci beaucoup pour votre réponse. – bjd2385