Python:如何覆盖子类中实例属性的类型提示?
Posted
技术标签:
【中文标题】Python:如何覆盖子类中实例属性的类型提示?【英文标题】:Python: how to override type hint on an instance attribute in a subclass? 【发布时间】:2020-01-25 02:37:18 【问题描述】:在深入研究之前,我的问题是:如何在子类中使用类型提示来指定实例属性的不同类型?
如果您不清楚这意味着什么,请阅读下文,我已在其中起草了一个示例来澄清问题。
完整说明
我有一个抽象类 Foo
,以及一个名为 SubclassOfFoo
的 Foo
的子类。
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_something
在Foo
内返回的类型提示
在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!是的,这两个也是有效的,赞成! 不幸的是,在我的实际用例中(不是我放在这里的理想化代码),Something
和 SubclassOfSomething
都在第 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_class
是SubclassOfSomething
而不是Something
。
【讨论】:
这确实解决了@sanyash 的问题。我很好奇,那个通用的方法叫什么? 另外,我不太明白它为什么会起作用。my_class
是 SubclassOfFoo
中的一个实例属性。将类型提示 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:如何覆盖子类中实例属性的类型提示?的主要内容,如果未能解决你的问题,请参考以下文章