Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
490 views
in Technique[技术] by (71.8m points)

python 3.x - 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?

Example:

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)

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"

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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

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:

  • We can assign obj = ClassA() since ClassA "is a" Base class.
  • We can use obj.do_something(BaseConfig()) since obj "is a" Base instance.

However, ClassA.do_something(config: ConfigA) says we cannot do both at the same time, contradicting the type equivalence.


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

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)

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...