How to specify f-bounded polymorphism in python typing ( i.e. refering to the type of a subclass )

72 Views Asked by At

An simple example of what I want to achieve:

from typing import TypeVar, Generic
from abc import abstractmethod

T = TypeVar( "T", bound="Copyable['T']" )

class Copyable( Generic[T] ):
    @abstractmethod
    def Copy( self ) -> T:
        pass

class Example( Copyable[ 'Example' ] ):
    def Copy( self ) -> 'Example':
        return Example()

However,

Pylance complains with: TypeVar bound type cannot be generic

MyPy complains with: error: Type variable "script.example.T" is unbound

Any ideas of how I could achieve this.

Extra details

The following is not a solution as it removes information. After copying an object of type Example its type would be reduced to Copyable.

class Copyable:
    @abstractmethod
    def Copy( self ) -> Copyable:
        pass

class Example( Copyable ):
    def Copy( self ) -> Copyable:
        return Example()

Context

I would like to use this for a class which takes callbacks who's first argument is the class it was given to. This class can be subclassed, meaning if the callback is of the type Callback[ [ Baseclass ], None ] then it will not have access ( without casting ) to methods of the derived class.

1

There are 1 best solutions below

0
On

I think what you're looking for is the Self type - this describes a method that returns the type of the class in it's current state (even if it goes through multiple inheritance):

from typing import Self
from abc import abstractmethod, ABC

class Copyable(ABC):
    @abstractmethod
    def Copy(self) -> Self:
        ...

class Example(Copyable):
    def Copy(self) -> Self:
        return self.__class__()

Via classmethod

Depending on the use case, you might also be able to implement Copy as a class method, to avoid using self.__class__.

from typing import Self
from abc import abstractmethod, ABC

class Copyable(ABC):
    @abstractmethod
    @classmethod
    def Copy(cls) -> Self:
        ...

class Example(Copyable):
    @classmethod
    def Copy(cls) -> Self:
        return cls()