2017-09-01 3 views
3

Angenommen, ich habe zwei Klassen Base und Child mit einer Factory-Methode in Base. Die Factory-Methode ruft eine andere Klassenmethode auf, die durch die untergeordneten Klassen Base überschrieben werden kann.Python 3 Typhinweis für eine Factory-Methode auf einer Basisklasse, die eine Kindklasseninstanz zurückgibt

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 

Gibt es eine Möglichkeit Base.create so zu beschriften, dass ein statisches Typprüfer ableiten könnte, dass Base.create() eine Instanz von Base zurückgegeben und Child.create() zurück eine Instanz Child, so dass das folgende Beispiel statische Analyse passieren würde?

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

Im obigen Beispiel würde ein statisches Typprüfer zu Recht beklagt, dass die method_not_present_on_base ist, na ja, nicht auf der Base Klasse.


Ich dachte über Base in eine generische Klasse drehen und die untergeordneten Klassen geben sich als Typ Argumente aufweisen, das heißt die CRTP zu Python zu bringen.

T = TypeVar('T') 

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

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

Aber das fühlt sich eher unpythonic mit CRTP von C++ kommen und alle ...

Antwort

2

Es ist in der Tat möglich: Die Funktion TypeVar with Generic Self genannt wird (obwohl dies etwas irreführend ist, weil wir für eine Verwendung sind diese Klassenmethode in diesem Fall). Ich glaube, es verhält sich ungefähr gleichwertig mit der "CRTP" -Technik, mit der Sie verbunden sind (obwohl ich kein C++ - Experte bin, kann also nicht sicher sagen).

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': ... 

Beachten Sie, dass:

In jedem Fall würden Sie Ihre Basis und Kind-Klassen wie so erklären

  1. Wir brauchen nicht die Klasse selbst generisch zu machen, da wir nur brauchen Eine generische Funktion
  2. Das Festlegen der an 'Base' gebundenen TypeVar ist streng genommen optional, aber ist wahrscheinlich eine gute Idee: Auf diese Weise können die Aufrufer Ihrer Basisklassen/Unterklassen zumindest Methoden aufrufen, die in der Basisklasse definiert sind selbst wenn du es tust nicht genau wissen, mit welcher Unterklasse du es zu tun hast.
  3. Wir können die Annotation auf cls für die untergeordnete Definition weglassen.
+0

Ehrfürchtig, danke! Unglücklicherweise behandelt PyCharm nun private Methoden auf "cls" als Zugriff auf ein privates Mitglied auf einem anderen Objekt, aber mypy behandelt dies wie erwartet. – PoByBolek

+0

Das ist wirklich cool! Vielen Dank für Ihre Antwort. – bjd2385

Verwandte Themen