python-3.x - 当参数可以具有从特定基类型派生的任何类型时,如何注释抽象方法的参数类型?

标签 python-3.x oop mypy python-3.8 python-typing

当参数可以具有从特定基类型派生的任何类型时,如何注释抽象方法的函数参数的类型?

例子:

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 解析这个时,我得到
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 派生的?

最佳答案

TLDR:创建基类 Generic并参数化配置类型:

C = TypeVar('C', bound=BaseConfig)

class Base(abc.ABC, Generic[C]):
    @abc.abstractmethod
    def do_something(self, config: C):
        pass

原始类层次结构声明 ClassA可以在任何地方使用 Base已验证。当我们假设一些变量 obj: Base ,这会导致冲突:
  • 我们可以分配 obj = ClassA()ClassA “是一个”Base类(class)。
  • 我们可以使用 obj.do_something(BaseConfig())obj “是一个”Base实例。

  • 然而,ClassA.do_something(config: ConfigA)说我们不能同时做这两个,这与类型等价相矛盾。

    相反,我们需要区分采用 Base 的“ConfigA”、采用 Base 的“ConfigB”等等。这是通过参数化来完成的 Base带有配置的类型变量。
    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 ”。由此,子类可以派生为采用适当的配置:
    class ClassA(Base[ConfigA]):        # set type variable to ConfigA
        def do_something(self, config: ConfigA):
            print("option_a: " + config.option_a)
    

    关于python-3.x - 当参数可以具有从特定基类型派生的任何类型时,如何注释抽象方法的参数类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62167179/

    相关文章:

    python - 如何高效地将数千张高清照片加载到 pandas df 中并转换为 HDF?

    javascript - 做点什么并继续上课

    java - 一个 Java 类最多应该有多少个实例变量?

    python - 为什么 mypy 在无法注释时提示列表理解?

    python - Mypy:以抽象类为值的 map 类型注释

    Python:如何按值为 bool 参数编写 typing.overload 装饰器

    python-2.7 - 如何在vs代码中为python2和python3配置pylint

    python - 为什么 if else 条件在 python 中执行这两个条件?

    python - pandas和Python中的iloc函数有什么优势

    javascript - 在php中计算每月包(产品)价格