Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python 3 type hint for a factory method on a base class returning a child class instance

Let's say I have two classes Base and Child with a factory method in Base. The factory method calls another classmethod which may be overriden by Base's child classes.

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 

Is there a way to annotate Base.create so that a static type checker could infer that Base.create() returned an instance of Base and Child.create() returned an instance of Child, so that the following example would pass static analysis?

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

In the above example a static type checker would rightfully complain that the method_not_present_on_base is, well, not present on the Base class.


I thought about turning Base into a generic class and having the child classes specify themselves as type arguments, i.e. bringing the CRTP to Python.

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

But this feels rather unpythonic with CRTP coming from C++ and all...

like image 844
PoByBolek Avatar asked Sep 01 '17 20:09

PoByBolek


People also ask

How do you type hints in Python?

Here's how you can add type hints to our function: Add a colon and a data type after each function parameter. Add an arrow ( -> ) and a data type after the function to specify the return data type.

What is a factory method in Python?

Factory Method is a creational design pattern used to create concrete implementations of a common interface. It separates the process of creating an object from the code that depends on the interface of the object. For example, an application requires an object with a specific interface to perform its tasks.

What is Classmethod?

The classmethod() is an inbuilt function in Python, which returns a class method for a given function.; Syntax: classmethod(function) Parameter :This function accepts the function name as a parameter. Return Type:This function returns the converted class method.

What is a factory class in Python?

A Class Factory is a function that creates and returns a class. It is one of the powerful patterns in Python.


1 Answers

It is indeed possible: the feature is called TypeVar with Generic Self (though this is slightly misleading because we're using this for a class method in this case). I believe it behaves roughly equivalently to the "CRTP" technique you linked to (though I'm not a C++ expert so can't say for certain).

In any case, you would declare your base and child classes like so:

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

Note that:

  1. We don't need to make the class itself generic since we only need a generic function
  2. Setting the TypeVar's bound to 'Base' is strictly speaking optional, but is probably a good idea: this way, the callers of your base class/subclasses will at least be able to call methods defined in the base class even if you don't know exactly which subclass you're dealing with.
  3. We can omit the annotation on cls for the child definition.
like image 170
Michael0x2a Avatar answered Sep 21 '22 17:09

Michael0x2a