10

I have an ABC with a method that subclasses should return with their own type, and I'm trying to figure out the best way to typehint this. For example:

from abc import ABC, abstractmethod

class Base(ABC):
    @abstractmethod
    def f(self): ## here i want a type hint for type(self)
        pass

class Blah(Base):
    def __init__(self, x: int):
        self.x = x

    def f(self) -> "Blah":
        return Blah(self.x + 1)

The best I could think of is this, which is a bit heavy:

from abc import ABC, abstractmethod
from typing import TypeVar, Generic

SELF = TypeVar["SELF"]

class Base(ABC, Generic[SELF]):

    @abstractmethod
    def f(self) -> SELF:
        pass

class Blah(Base["Blah"]):

    def __init__(self, x: int):
        self.x = x

    def f(self) -> "Blah":
        return Blah(self.x+1)

I there a better/cleaner way?

mrip
  • 14,913
  • 4
  • 40
  • 58
  • All you need is a forward reference to the ABC. For class methods, where an instance of the `cls` type is returned, see [Can you annotate return type when value is instance of cls?](https://stackoverflow.com/q/39205527). – Martijn Pieters Mar 07 '21 at 00:40

1 Answers1

1

Using python 3.7 it works by importing annotations from __future__

from __future__ import annotations

class Base():
    def f(self) -> Base: ## Here the type is Base since we can not guarantee it is a Blah
        pass

class Blah(Base):
    def __init__(self, x: int):
        self.x = x

    def f(self) -> Blah: ## Here we can be more specific and say that it is a Blah
        return Blah(self.x + 1)
Cedric
  • 199
  • 4
  • 2
    Thank you for your answer. However, this forces you to overwrite `f` in every subclass, even if the contents is exactly the same – mousetail Mar 03 '21 at 08:15
  • I think it is normal that you have to overwrite the function to get a different signature – Cedric Mar 03 '21 at 22:12
  • Not if a function should simply return self, like if you use the chaining pattern, or for `__enter__` methods – mousetail Mar 04 '21 at 07:58
  • But using the chaining pattern I guess it is sufficient to say that the function return Base each time? And you will not need to change the signature of the function, right? – Cedric Mar 04 '21 at 14:14
  • That would be a problem since we could not call other methods defined only on `Blah` from the return value of `f` – mousetail Mar 04 '21 at 14:34
  • I'm awarding you the bounty now unless someone gives a better answer now. You answered the original question nicely and I really my real question is significantly different enough that hijacking this one might not have been the right call. – mousetail Mar 06 '21 at 16:22
  • You don’t need the `from __future__` switch, you can use forward references too. See [Name not defined in type annotation](https://stackoverflow.com/q/36286894). – Martijn Pieters Mar 07 '21 at 00:35
  • 1
    I've also seen in the base class `def f(self: T) -> T:`, with `T` being a TypeVar. This way, the base class doesn't need to be made Generic. It can also work with classmethod with `cls: Type[T]`. – PhilMacKay Sep 28 '22 at 13:25