Python:如何覆盖子类中实例属性的类型提示?

Posted

技术标签:

【中文标题】Python:如何覆盖子类中实例属性的类型提示?【英文标题】:Python: how to override type hint on an instance attribute in a subclass? 【发布时间】:2020-01-25 02:37:18 【问题描述】:

在深入研究之前,我的问题是:如何在子类中使用类型提示来指定实例属性的不同类型?

如果您不清楚这意味着什么,请阅读下文,我已在其中起草了一个示例来澄清问题。


完整说明

我有一个抽象类 Foo,以及一个名为 SubclassOfFooFoo 的子类。

Foo 有一个抽象方法get_something,它返回一个Something 类型的对象。

Something 有一个名为SubclassOfSomething 的子类。 SubclassOfSomething 有一个额外的方法something_special

SubclassOfFoo 覆盖 get_something 以返回 SubclassOfSomething 类型的对象。然后,SubclassOfFoo 尝试使用SubclassOfSomething 的方法something_special

但是,目前我的 PyCharm 的检查报告为 Unresolved attribute reference 'something_special' for class 'Something'。我正在尝试找出解决此问题的正确方法。

这一切都非常令人困惑,所以我编写了一个很好的小代码 sn-p 来帮助这里:

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()

基本上,为了让一切顺利进行,我可以做以下几件事之一:

    删除get_somethingFoo 内返回的类型提示 在SubclassOfFoo 中为self.my_class 使用类型提示来清除问题 使用泛型?

选项 1. 是我想要避免的

选项 2. 还不错,但我想不通

选项 3. 也是一个选项。

我也愿意接受其他选择,因为我确信有更好的方法。

您能帮我找出正确的处理方法吗?


我的尝试

为了模拟选项 2,我尝试使用 typing.Type,如下所示:Subclass in type hinting

但是,这对我不起作用。

【问题讨论】:

@101arrowz:该类型提示存在是因为更具体的返回类型信息很重要。不能假定Something 的任意实例具有something_special 方法。 我怀疑最好的选择是使Foo 通用。 【参考方案1】:

您也可以在Something 上提供something_special 方法,并引发NotImplementedError

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

    def something_special(self):
        raise NotImplementedError()

这解决了您的类型提示问题,尽管在功能上它会在同一点引发异常(如果您设法以某种方式获得Something 并尝试调用something_special,则将是NotImplementedError 而不是@987654328 @)。

也许在某些情况下,您可能只想使用pass,具体取决于something_special 的实际含义。

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()

重要的基础是确保您的类层次结构符合公共接口 - 子类上的公共方法而不是父类上的公共方法违背了这一点,并减少了类层次结构中对象的多态性。

【讨论】:

感谢您的回答@RachSharp!是的,这两个也是有效的,赞成! 不幸的是,在我的实际用例中(不是我放在这里的理想化代码),SomethingSubclassOfSomething 都在第 3 方包中,我希望有类型提示在我自己的代码中处理【参考方案2】:

您可以在类定义的开头给出my_class 属性的类型提示:

class SubclassOfFoo(Foo):
    my_class: SubclassOfSomething  # <- here

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

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

之后,PyCharm 检查没有警告Unresolved attribute reference 'something_special' for class 'Something',因为现在已知my_classSubclassOfSomething 而不是Something

【讨论】:

这确实解决了@sanyash 的问题。我很好奇,那个通用的方法叫什么? 另外,我不太明白它为什么会起作用。 my_classSubclassOfFoo 中的一个实例属性。将类型提示 my_class: SubclassOfSomething 放在 __init__ 内并不能解决问题。它仅在放入SubclassOfFoo 的类范围时才解决问题。这是为什么? self.my_class 不应该被限定在实例的范围内,并且不受类范围内的类型提示的影响吗? @IntrastellarExplorer 请检查此 PEP:python.org/dev/peps/pep-0526/…。据说在类范围内描述为my_class: SubclassOfSomething的变量被视为实例属性,而my_class: ClassVar[SubclassOfSomething]被视为类变量,而不是实例属性。 好的,我明白了,再次感谢@sanyash!这是一个很好的功能【参考方案3】:

使用泛型:

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()

【讨论】:

感谢@user2235698 对语法进行散列!这在我的 PyCharm 中可以正常工作。我会保留这个以备将来参考

以上是关于Python:如何覆盖子类中实例属性的类型提示?的主要内容,如果未能解决你的问题,请参考以下文章

Python 类型提示:如何判断 X 是 Foo 的子类?

如何提示编译器使用 UIView 子类

Python 类型提示 - 为 dict 子类指定键、值类型

Python 3.5类型提示动态生成的实例属性

如何用子类内部状态包装所有 python 超类方法?

在 TypeScript 的子类中自动覆盖多个函数的返回类型