1

How can I implement a custom constructor (class method) that is inheritable in python?

The following minimized example might give an idea:

from dataclasses import dataclass
from typing import Type, TypeVar

T = TypeVar("T")


@dataclass
class Parent:
    something: int = 2

    @classmethod
    def from_float(cls: Type[T], something_as_float: float) -> T:
        return Type[T](something=int(something_as_float))


@dataclass
class Child(Parent):
    """ Should also be constructible via from_float
    """


assert isinstance(Parent.from_float(1.0), Parent)
assert isinstance(Child.from_float(1.0), Child)

mypy does not like the constructor I call when returning from from_float. I don't know how to refer to class (Parent or Child) from the class method.

7
  • You mean like an instance factory? Commented Sep 28, 2021 at 13:02
  • not sure... adding a C++ pattern so that I can implement a Rust pattern in python feels like I would just be adding fuel to the garbage fire though :) Commented Sep 28, 2021 at 13:06
  • Why don't you implement the __new__ method ? Commented Sep 28, 2021 at 13:07
  • The @dataclass implicitly adds the constructor (and __new__too?) that takes the fields, and I can't disturb that. I'm trying to add rust inspired conversion methods onto some existing dataclasses that inherit from each other, and I can't change the dataclasses-with-inheritance part of the design. Commented Sep 28, 2021 at 13:11
  • 1
    Type[T] is not the constructor; cls is. Commented Sep 28, 2021 at 13:17

1 Answer 1

3

Pass the bound argument to TypeVar to specify that the type is a subclass of Parent. This lets mypy know that the type has the something attribute

When you create the instance use cls not Type[T]

from dataclasses import dataclass
from typing import Type, TypeVar

T = TypeVar("T", bound='Parent')


@dataclass
class Parent:
    something: int = 2

    @classmethod
    def from_float(cls: Type[T], something_as_float: float) -> T:
        return cls(something=int(something_as_float))


@dataclass
class Child(Parent):
    pass


assert isinstance(Parent.from_float(1.0), Parent)
assert isinstance(Child.from_float(1.0), Child)
Sign up to request clarification or add additional context in comments.

2 Comments

Whoooo that did it. Thanks! I can confirm locally that it runs with out asserting, and also passes mypy!
You can't refer a class within its definition... and escaping as cls: "Parent" and -> "Parent" didn't help either. I hit "The erased type of self "minimized_typing_puzzle.Parent" is not a supertype of its class "Type[minimized_typing_puzzle.Parent]"" and then error: "Parent" not callable. Iain's solution works, but feel free to keep the thread going if you enjoy typing puzzles. Thanks!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.