下面是两个类树。 每个都有一个基类,吃掉其邻居类的基类。
一旦派生到具体类,我也使用邻居类型作为依赖项。 该代码按预期工作,但是类型检查器抱怨道。 (我使用的是pycharm)
我认为从具体类型向下转型为基类型是可以的,因为基类始终只适用于向下转型的类型,而不依赖于具体类型的具体方面。
from typing import reveal_type
class BaseView:
...
class BaseController:
def __init__(self, view: BaseView):
self.view = view
class ConcreteView(BaseView):
attr = "42"
class ConcreteController(BaseController):
def __init__(self, view: ConcreteView):
super().__init__(view)
def meth(self):
# gives an
# Unresolved attribute reference 'attr' for class 'BaseView'
# warning
print(self.view.attr)
# okay
print(reveal_type(self.view))
if __name__ == '__main__':
concrete_view = ConcreteView()
controller = ConcreteController(concrete_view)
controller.meth()
一半的解决方案是使用让打字检查员信服的工厂 然而,linter 仍然无法解决。 另外,工厂模式不是我喜欢的,它使事情变得复杂,只要满足类型检查器
from typing import reveal_type, Callable
class BaseView:
...
class BaseController:
def __init__(self, view_factory: Callable):
self.view = view_factory()
class ConcreteView(BaseView):
attr = "42"
def __init__(self):
self.b = 10
class ConcreteController(BaseController):
def __init__(self, view_factory: Callable[[], ConcreteView]):
super().__init__(view_factory)
def meth(self):
# warning gone but lint still not working
print(self.view.attr)
print(self.view.b)
# okay
print(reveal_type(self.view))
if __name__ == '__main__':
controller = ConcreteController(lambda: ConcreteView())
controller.meth()
另外我已经玩过通用了。
from typing import reveal_type, Generic, TypeVar
T = TypeVar("")
class BaseView(Generic[T]):
...
class BaseController(Generic[T]):
def __init__(self, view: T):
self.view = view
class ConcreteView(BaseView):
attr = "42"
class ConcreteController(BaseController):
def __init__(self, view: BaseView[ConcreteView]):
super().__init__(view)
def meth(self):
# no warning typchecker
print(self.view.attr)
# but no lint
self.view
# okay
print(reveal_type(self.view))
if __name__ == '__main__':
concrete_view = ConcreteView()
controller = ConcreteController(concrete_view)
controller.meth()
因此,泛型的简单应用使得此过程既可以通过 mypy 也可以通过pyright,并且显示的类型是
ConcreteVeiw
:
import typing
class BaseView:
pass
T = typing.TypeVar("T", bound=BaseView)
class BaseController(typing.Generic[T]):
def __init__(self, view: T):
self.view = view
class ConcreteView(BaseView):
attr = "42"
class ConcreteController(BaseController[ConcreteView]):
def __init__(self, view: ConcreteView):
super().__init__(view)
def meth(self) -> None:
print(self.view.attr)
typing.reveal_type(self.view)
这可能是我的第一直觉,但请注意,以下没有泛型的版本只需在
view: ConcreteView
also中注释
ConcreteController
即可:
import typing
class BaseView:
pass
class BaseController:
def __init__(self, view: BaseView):
self.view = view
class ConcreteView(BaseView):
attr = "42"
class ConcreteController(BaseController):
view: ConcreteView
def __init__(self, view: ConcreteView):
super().__init__(view)
def meth(self) -> None:
print(self.view.attr)
typing.reveal_type(self.view)
if __name__ == "__main__":
concrete_view = ConcreteView()
controller = ConcreteController(concrete_view)
controller.meth()
我当前的笔记本电脑上没有 PyCharm。但我觉得,如果在上述任何一种情况下都无法推断出
ConcreteView
,那么可能是时候更换编辑器了。或者简单地依靠 mypy
或 pyright
进行静态分析。公平地说,如果没有明确的 view: ConcreteView
,pyright 和 mypy 就会失败并抱怨您正在使用 BaseView
。但在这种情况下,明确地帮助类型推断总是好的。
您的最后一次尝试已接近尾声。但是,您并没有真正使用泛型在具体类中键入视图。
from typing import Generic, TypeVar
T = TypeVar("T")
class BaseView:
...
class BaseController(Generic[T]):
def __init__(self, view: T) -> None:
self.view = view
class ConcreteView(BaseView):
attr = "42"
class ConcreteController(BaseController[ConcreteView]):
def __init__(self, view: ConcreteView) -> None:
super().__init__(view)
def meth(self) -> None:
# no warning typchecker
print(self.view.attr)
# but no lint
self.view
# okay
print(type(self.view))
if __name__ == "__main__":
concrete_view = ConcreteView()
controller = ConcreteController(concrete_view)
controller.meth()
BaseController
模板中的类型。attr
属性)。