当参数可以具有从特定基类型派生的任何类型时,如何注释抽象方法的参数类型? [英] How do I annotate the type of a parameter of an abstractmethod, when the parameter can have any type derived from a specific base type?
问题描述
当参数可以具有从特定基类型派生的任何类型时,如何注释抽象方法的函数参数的类型?
How do I annotate the type of a function parameter of a abstractmethod, when the parameter can have any type derived from a specific base type?
示例:
import abc
import attr
@attr.s(auto_attribs=True)
class BaseConfig(abc.ABC):
option_base: str
@attr.s(auto_attribs=True)
class ConfigA(BaseConfig):
option_a: str
@attr.s(auto_attribs=True)
class ConfigB(BaseConfig):
option_b: bool
class Base(abc.ABC):
@abc.abstractmethod
def do_something(self, config: BaseConfig):
pass
class ClassA(Base):
def do_something(self, config: ConfigA):
# test.py:27: error: Argument 1 of "do_something" is incompatible with supertype "Base"; supertype defines the argument type as "BaseConfig"
print("option_a: " + config.option_a)
class ClassB(Base):
def do_something(self, config: ConfigB):
# test.py:33: error: Argument 1 of "do_something" is incompatible with supertype "Base"; supertype defines the argument type as "BaseConfig"
print("option_b: " + str(config.option_b))
conf_a = ConfigA(option_a="value_a", option_base="value_base")
conf_b = ConfigB(option_b=True, option_base="value_base")
object_a = ClassA()
object_b = ClassB()
object_a.do_something(conf_a)
object_b.do_something(conf_b)
用 mypy 解析时我得到
When parsing this with mypy I get
test.py:27: error: Argument 1 of "do_something" is incompatible with supertype "Base"; supertype defines the argument type as "BaseConfig"
test.py:33: error: Argument 1 of "do_something" is incompatible with supertype "Base"; supertype defines the argument type as "BaseConfig"
我需要如何更改 Base.do_something() 的签名,以便 mypy 不报告任何错误,同时仍然强制执行抽象方法 do_something 的函数参数是从 BaseConfig 派生的?
How would I need to change the signature of Base.do_something() so mypy doesn't report any error, while still enforcing, that the function parameter of the abstract method do_something is derived from BaseConfig?
推荐答案
TLDR:使基类 Generic
并参数化配置类型:
TLDR: Make the baseclass Generic
and parameterise the type of configuration:
C = TypeVar('C', bound=BaseConfig)
class Base(abc.ABC, Generic[C]):
@abc.abstractmethod
def do_something(self, config: C):
pass
<小时>
原始类层次结构声明ClassA
可以在任何Base
有效的地方使用.当我们假设某个变量 obj: Base
时,这会导致冲突:
The original class hierarchy declares that ClassA
can be used anywhere Base
is valid. When we assume some variable obj: Base
, this leads to a conflict:
- 我们可以赋值
obj = ClassA()
因为ClassA
是一个"Base
类. - 我们可以使用
obj.do_something(BaseConfig())
因为obj
是一个"Base
实例.
- We can assign
obj = ClassA()
sinceClassA
"is a"Base
class. - We can use
obj.do_something(BaseConfig())
sinceobj
"is a"Base
instance.
但是,ClassA.do_something(config: ConfigA)
说我们不能同时做这两个事情,这与类型等效性相矛盾.
However, ClassA.do_something(config: ConfigA)
says we cannot do both at the same time, contradicting the type equivalence.
相反,我们需要区分采用 ConfigA
的Base
"和采用 ConfigB
的Base
"代码>"等.这是通过使用配置的类型变量参数化 Base
来完成的.
Instead, we need to distinguish between "Base
that takes a ConfigA
", "Base
that takes a ConfigB
" and so on. This is done by parameterising Base
with a type-variable for the config.
from typing import Generic, TypeVar
C = TypeVar('C', bound=BaseConfig) # C "is some" BaseConfig type
class Base(abc.ABC, Generic[C]): # class takes type variable ...
@abc.abstractmethod
def do_something(self, config: C): # ... and uses it in method signature
pass
这允许我们拥有通用和具体的 Base
变体 - 例如,Base[ConfigA]
是一个Base
,它需要一个ConfigA
".由此,子类可以派生为采用适当的配置:
This allows us to have both generic and concrete Base
variants - for example, Base[ConfigA]
is a "Base
that takes a ConfigA
". From this, the subclasses can be derived as taking the appropriate configuration:
class ClassA(Base[ConfigA]): # set type variable to ConfigA
def do_something(self, config: ConfigA):
print("option_a: " + config.option_a)
这篇关于当参数可以具有从特定基类型派生的任何类型时,如何注释抽象方法的参数类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!