问题
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:
- Remove the type hint on the return of get_somethingwithinFoo
- Use a type hint in SubclassOfFooforself.my_classto clear things up
- 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.
回答1:
You can give a type hint on my_class attribute in the beginning of class definition:
class SubclassOfFoo(Foo):
    my_class: SubclassOfSomething  # <- here
    def get_something(self) -> SubclassOfSomething:
        return SubclassOfSomething()
    def do_something_special(self):
        self.my_class.something_special()
After that there is no warning Unresolved attribute reference 'something_special' for class 'Something' from PyCharm inspection because now my_class is known to be SubclassOfSomething not Something.
回答2:
You could provide the something_special method on Something too, and raise a NotImplementedError
class Something:
    def __init__(self):
        self.attr = 0
    def something_special(self):
        raise NotImplementedError()
This resolves your type hinting issue, although functionally it will raise an exception at the same point (if you managed to get a Something somehow and try to call something_special, just will be NotImplementedError instead of AttributeError).
Maybe in some situations you might want to just pass instead, depending on what something_special actually is.
class Something:
    def __init__(self):
        self.attr = 0
    def validate(self):
        # doesn't want to perform validation
        pass
class SubclassOfSomething(Something):
    def __init__(self):
        Something.__init__(self)
    def validate(self):
        if self.attr < 0:
            raise ValueError()
The important underlying thing is making sure your class hierarchy conforms to a common interface - public methods on subclasses but not on parents goes against that and reduces the polymorphism of objects in your class hierarchy.
回答3:
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()
来源:https://stackoverflow.com/questions/58089300/python-how-to-override-type-hint-on-an-instance-attribute-in-a-subclass