Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python: how to override type hint on an instance attribute in a subclass?

Before you dive in, here is my question: how can I use type hints in a subclass to specify a different type on an instance attribute?

If you are unclear on what that means, read below, where I have drawn up an example to clarify things.


Full Explanation

I have an abstract class Foo, and a subclass of Foo called SubclassOfFoo.

Foo has an abstract method get_something that returns an object of type Something.

Something has a subclass called SubclassOfSomething. SubclassOfSomething has an additional method something_special.

SubclassOfFoo overrides get_something to return an object of type SubclassOfSomething. Then, SubclassOfFoo tries to use SubclassOfSomething's method something_special.

However, currently my PyCharm's inspections are reporting Unresolved attribute reference 'something_special' for class 'Something'. I am trying to figure out the correct way to fix this.

This is all very confusing, so I have made a nice little code snippet to help here:

from abc import ABC, abstractmethod


class Something:
    def __init__(self):
        self.attr = 0


class SubclassOfSomething(Something):
    def __init__(self):
        Something.__init__(self)

    def something_special(self):
        self.attr = 1


class Foo(ABC):
    def __init__(self):
        self.my_class = self.get_something()

    @abstractmethod
    def get_something(self) -> Something:
        pass


class SubclassOfFoo(Foo):
    def __init__(self):
        Foo.__init__(self)

    def get_something(self) -> SubclassOfSomething:
        return SubclassOfSomething()

    def do_something_special(self):
        self.my_class.something_special()

Basically, in order to get everything to work out, I can do one of several things:

  1. Remove the type hint on the return of get_something within Foo
  2. Use a type hint in SubclassOfFoo for self.my_class to clear things up
  3. Use generics?

Option 1. is what I am trying to avoid

Option 2. is not bad, but I can't figure it out

Option 3. is also an option.

I am also open to other options, as I am sure there is a better way.

Can you please help me figure out the correct way to handle this?


What I Have Tried

To emulate option 2., I tried using typing.Type as suggested here: Subclass in type hinting

However, this was not working for me.

like image 317
Intrastellar Explorer Avatar asked Sep 24 '19 23:09

Intrastellar Explorer


People also ask

What are class and instance attributes in Python?

Class & Instance Attributes in Python. Class attributes. Class attributes belong to the class itself they will be shared by all the instances. Such attributes are defined in the class body parts usually at the top, for legibility.

How do you use type hints in Python?

In a type hint, if we specify a type (class), then we mark the variable as containing an instance of that type. To specify that a variable instead contains a type, we need to use type [Cls] (or the old syntax typing.Type ). We need to add type hints for make_animal ().

How to override the method in the Super-class?

When a method in a subclass has the same name, same parameters or signature and same return type (or sub-type) as a method in its super-class, then the method in the subclass is said to override the method in the super-class. Attention geek! Strengthen your foundations with the Python Programming Foundation Course and learn the basics.

What is overriding in Python?

Prerequisite: Inheritance in Python Method overriding is an ability of any object-oriented programming language that allows a subclass or child class to provide a specific implementation of a method that is already provided by one of its super-classes or parent classes.


1 Answers

Using generics:

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


SomethingT = TypeVar('SomethingT', bound='Something')


...


class Foo(ABC, Generic[SomethingT]):
    my_class: SomethingT

    def __init__(self):
        self.my_class = self.get_something()

    @abstractmethod
    def get_something(self) -> SomethingT:
        pass


class SubclassOfFoo(Foo[SubclassOfSomething]):
    def __init__(self):
        super().__init__()

    def get_something(self) -> SubclassOfSomething:
        return SubclassOfSomething()

    def do_something_special(self):
        # inferred type of `self.my_class` will be `SubclassOfSomething`
        self.my_class.something_special()
like image 199
user2235698 Avatar answered Sep 24 '22 06:09

user2235698