更正抽象 class 实现的属性类型
Correct attribute types of abstract class implementation
抱歉标题令人困惑,但我仍在寻找描述我的案例的好方法。基本上,我想继承一个以另一个抽象 class 作为参数的抽象 class,但是我怎样才能使其实现的属性正确识别其参数类型?
是不是很迷惑?这就是我的意思:
from abc import ABC
from abc import abstractmethod
from dataclasses import dataclass
# Define abstract classes
class Params(ABC):
"""An abstract class for storing parameters."""
pass
class Solver(ABC):
"""An abstract class to solve a problem with a specific parameter set."""
def __init__(self, params: Params) -> None:
"""Initialize with a Params object."""
self.params = params
@abstractmethod
def solve(self) -> None:
"""An abstract method that must be implemented in the subclasses."""
pass
# Implementation of abstract classes
@dataclass
class ParamsX(Params):
"""A subclass of Params which has attributes first_param and second_param."""
first_param: int
second_param: list[float]
class SolverX(Solver):
"""A subclass of Solver which solve the problem X."""
def __init__(self, params: ParamsX) -> None:
"""Initialize with the ParamsX object."""
super().__init__(params)
def solve(self) -> None:
"""Solve the problem X."""
print(self.params) # The params is recognized as Params
print(self.params.first_param) # The first_param is recognized as Any
print(self.params.second_param) # The second_param is recognized as Any
# There're also ParamsY and SolverY, ParamsZ and SolverZ, etc
我的问题:如何让first_param
和second_param
分别显示为int
和list[float]
类型?我没有检查 mypy
但 the Visual Studio Code indicates that.
一次尝试:
如果我使用下面的代码,类型是正确的,但我不认为这是 pythonic 方式,它就像抽象 class Params
没有任何目的完全没有。
class SolverX(Solver):
"""A subclass of Solver which takes a ParamsX object as argument."""
def __init__(self, params: ParamsX) -> None:
super().__init__(params)
self.params: ParamsX # Forced annotation
如何在 class 级别指定变量:
class SolverX(Solver):
"""A subclass of Solver which solves the problem X."""
params: ParamsX
def __init__(self, params: ParamsX) -> None:
"""Initialize with the ParamsX object."""
super().__init__(params)
非常感谢 TeamSpen210 给我这个答案:
What you need is a Generic
, not necessarily an ABC
.
Change Solver
to be like this:
ParamT = TypeVar('ParamT', bound=Params)
class Solver(ABC, Generic[ParamT]):
def __init__(self, param: ParamT) -> None:
self.params = param
class SolverX(Solver[ParamsX]):
....
TypeVar
defines a type variable, which you later substitute with a specific type, bound restricts it to only permit subclasses of the specified type. Then having the class "subclass" Generic
means that the variable is scoped to the whole class. So then when you use the subscripted version, it's as if the type variable was replaced by the real class.
Note the other way you can use it is specific to a function - you put it in the signature, then it takes on whatever type you use to call the function.
The MyPy docs have a lot more info about how things work, as well as say PEP484:
https://mypy.readthedocs.io/en/stable/generics.html
There's also typeshed, the type definitions for the standard library:
https://github.com/python/typeshed/tree/master/stdlib
抱歉标题令人困惑,但我仍在寻找描述我的案例的好方法。基本上,我想继承一个以另一个抽象 class 作为参数的抽象 class,但是我怎样才能使其实现的属性正确识别其参数类型?
是不是很迷惑?这就是我的意思:
from abc import ABC
from abc import abstractmethod
from dataclasses import dataclass
# Define abstract classes
class Params(ABC):
"""An abstract class for storing parameters."""
pass
class Solver(ABC):
"""An abstract class to solve a problem with a specific parameter set."""
def __init__(self, params: Params) -> None:
"""Initialize with a Params object."""
self.params = params
@abstractmethod
def solve(self) -> None:
"""An abstract method that must be implemented in the subclasses."""
pass
# Implementation of abstract classes
@dataclass
class ParamsX(Params):
"""A subclass of Params which has attributes first_param and second_param."""
first_param: int
second_param: list[float]
class SolverX(Solver):
"""A subclass of Solver which solve the problem X."""
def __init__(self, params: ParamsX) -> None:
"""Initialize with the ParamsX object."""
super().__init__(params)
def solve(self) -> None:
"""Solve the problem X."""
print(self.params) # The params is recognized as Params
print(self.params.first_param) # The first_param is recognized as Any
print(self.params.second_param) # The second_param is recognized as Any
# There're also ParamsY and SolverY, ParamsZ and SolverZ, etc
我的问题:如何让first_param
和second_param
分别显示为int
和list[float]
类型?我没有检查 mypy
但 the Visual Studio Code indicates that.
一次尝试:
如果我使用下面的代码,类型是正确的,但我不认为这是 pythonic 方式,它就像抽象 class Params
没有任何目的完全没有。
class SolverX(Solver):
"""A subclass of Solver which takes a ParamsX object as argument."""
def __init__(self, params: ParamsX) -> None:
super().__init__(params)
self.params: ParamsX # Forced annotation
如何在 class 级别指定变量:
class SolverX(Solver):
"""A subclass of Solver which solves the problem X."""
params: ParamsX
def __init__(self, params: ParamsX) -> None:
"""Initialize with the ParamsX object."""
super().__init__(params)
非常感谢 TeamSpen210 给我这个答案:
What you need is a
Generic
, not necessarily anABC
.Change
Solver
to be like this:ParamT = TypeVar('ParamT', bound=Params) class Solver(ABC, Generic[ParamT]): def __init__(self, param: ParamT) -> None: self.params = param class SolverX(Solver[ParamsX]): ....
TypeVar
defines a type variable, which you later substitute with a specific type, bound restricts it to only permit subclasses of the specified type. Then having the class "subclass"Generic
means that the variable is scoped to the whole class. So then when you use the subscripted version, it's as if the type variable was replaced by the real class.Note the other way you can use it is specific to a function - you put it in the signature, then it takes on whatever type you use to call the function.
The MyPy docs have a lot more info about how things work, as well as say PEP484: https://mypy.readthedocs.io/en/stable/generics.html
There's also typeshed, the type definitions for the standard library: https://github.com/python/typeshed/tree/master/stdlib