将单独的分支组合成公共结构的设计模式

问题描述 投票:0回答:3

我有由数据加载器和数据转换器组成的应用程序。每个加载器和每个转换器都是抽象基本加载器和抽象基本转换器的子类,我将在下面的示例中省略它们。具体的加载器和变压器之间存在 1:1 映射,即知道哪个加载器和变压器属于一起。

假设我们有两个加载器和两个变压器,处理数据

class Data1: ...

class Data2: ...


class Loader1:
    def get_data(self) -> Data1: ...

class Loader2:
    def get_data(self) -> Data2: ...


class Transformer1:
    def transform_data(self, data: Data1) -> None: ...

class Transformer2:
    def transform_data(self, data: Data2) -> None: ...

这些类现在可以组合到应用程序中

class App1:
    Loader = Loader1
    Transformer = Transformer1

class App2:
    Loader = Loader2
    Transformer = Transformer2

有配套工厂

from typing import Union, Type

def make_app(use_app1: bool) -> Union[Type[App1], Type[App2]]:
    if use_app1:
        return App1
    else:
        return App2

这就是我想使用上面的方式

def main(use_app1: bool) -> None:
    app = make_app(use_app1)
    loader = app.Loader()
    data = loader.get_data()
    transformer = app.Transformer()
    transformer.transform_data(data=data)

然而,mypy 抱怨

error: Argument "data" to "transform_data" of "Transformer1" has incompatible type "Union[Data1, Data2]"; expected "Data1"  [arg-type]
error: Argument "data" to "transform_data" of "Transformer2" has incompatible type "Union[Data1, Data2]"; expected "Data2"  [arg-type]

有没有办法让

mypy
相信分支
Loader1 -> Data1 -> Transformer1
Loader2 -> Data2 -> Transformer2
是分开的,不会混合在一起?

是否有可用于此用例的替代模式?

python design-patterns python-typing mypy
3个回答
1
投票

好的,这是解决这个问题的第三次尝试。在这次尝试中,我使用抽象协议告诉 MyPy,事实上,在许多这些函数中,返回什么特定类型并不重要,只要返回的对象具有特定的接口即可. from typing import Type, Protocol, cast ### ABSTRACT INTERFACES ### class DataProto(Protocol): ... class LoaderProto(Protocol): def get_data(self) -> DataProto: ... class TransformerProto(Protocol): def transform_data(self, data: DataProto) -> None: ... class AppProto(Protocol): Loader: Type[LoaderProto] Transformer: Type[TransformerProto] ### CONCRETE IMPLEMENTATIONS ### class Data1: ... class Data2: ... class Loader1: def get_data(self) -> Data1: ... class Loader2: def get_data(self) -> Data2: ... class Transformer1: def transform_data(self, data: Data1) -> None: ... class Transformer2: def transform_data(self, data: Data2) -> None: ... class App1: Loader = Loader1 Transformer = Transformer1 class App2: Loader = Loader2 Transformer = Transformer2 GenericAppClassType = Type[AppProto] def make_app(use_app1: bool) -> GenericAppClassType: if use_app1: return cast(GenericAppClassType, App1) else: return cast(GenericAppClassType, App2) def main(use_app1: bool) -> None: app = make_app(use_app1) loader = app.Loader() data = loader.get_data() transformer = app.Transformer() transformer.transform_data(data=data)

在我的游乐场
这里

尝试一下。


1
投票
Data1

Data2 具有相同的接口等:

from abc import abstractmethod, ABCMeta
from typing import Union, Type, TypeVar, Any, Protocol


### ABSTRACT INTERFACES ###


class AbstractData:
    __slots__ = ()
    

class AbstractLoader(metaclass=ABCMeta):
    __slots__ = ()
    
    @abstractmethod
    def get_data(self) -> AbstractData: ...
    

D = TypeVar('D', bound=AbstractData, contravariant=True)
    

class AbstractTransformer(Protocol[D]):
    __slots__ = ()
    
    @abstractmethod
    def transform_data(self, data: D) -> None: ...


L = TypeVar('L', bound=AbstractLoader, covariant=True)
T = TypeVar('T', bound=AbstractTransformer[Any], covariant=True)


class AbstractApp(Protocol[L, T]):
    __slots__ = ()
    
    @classmethod
    @property
    @abstractmethod
    def Loader(cls) -> Type[L]: ...
    
    @classmethod
    @property
    @abstractmethod
    def Transformer(cls) -> Type[T]: ...
    

### CONCRETE IMPLEMENTATIONS ###
    

class Data1(AbstractData): ...


class Data2(AbstractData): ...


class Loader1(AbstractLoader):
    def get_data(self) -> Data1: ...


class Loader2(AbstractLoader):
    def get_data(self) -> Data2: ...


class Transformer1(AbstractTransformer[Data1]):
    def transform_data(self, data: Data1) -> None: ...


class Transformer2(AbstractTransformer[Data2]):
    def transform_data(self, data: Data2) -> None: ...


class App1(AbstractApp[Loader1, Transformer1]):
    Loader = Loader1
    Transformer = Transformer1


class App2(AbstractApp[Loader2, Transformer2]):
    Loader = Loader2
    Transformer = Transformer2
    

def make_app(use_app1: bool) -> Type[AbstractApp[Any, Any]]:
    if use_app1:
        return App1
    else:
        return App2


def main(use_app1: bool) -> None:
    app = make_app(use_app1)
    loader = app.Loader()
    data = loader.get_data()
    transformer = app.Transformer()
    transformer.transform_data(data=data)
    

这里的问题是,您的 
make_app

0
投票
use_app1

True
,则一个签名;如果
use_app1
False
,则另一个签名。
typing.overload
typing.Literal
结合使用是这里的解决方案,因为
@overload
允许我们注册一个函数的多个不同签名。用
@overload
修饰的函数的实现在运行时会被忽略——它们仅用于类型检查器——因此这些函数的主体可以留空。一般来说,您只需在这些函数的主体中添加文字省略号
...
或文档字符串即可。必须至少有一个未用
@overload
修饰的函数的具体实现,以便在运行时使用。
from typing import overload, Literal, Union, Type

App1Type = Type[App1]
App2Type = Type[App2]

@overload
def make_app(use_app1: Literal[True]) -> App1Type:
    """Signature of the function when `use_app1` is True"""

@overload
def make_app(use_app1: Literal[False]) -> App2Type:
    """Signature of the function when `use_app1` is False"""

def make_app(use_app1: bool) -> Union[App1Type, App2Type]:
    """Concrete implementation of the function, for use at runtime"""

    if use_app1:
        return App1
    else:
        return App2

typing.overload
的文档位于
这里

typing.Literal
的文档位于 here

编辑

:看起来这不起作用。您可以通过像这样重写
main

函数来使其工作,但一定有比执行真正毫无意义的 if-else 语句更好的方法...

def main(use_app1: Literal[True, False]) -> None:
    app: Union[App1Type, App2Type]
    loader: Union[Loader1, Loader2]
    data: Union[Data1, Data2]
    transformer: Union[Transformer1, Transformer2]
    
    if use_app1:
        app = make_app(use_app1)
        loader = app.Loader()
        data = loader.get_data()
        transformer = app.Transformer()
        transformer.transform_data(data=data)
    else:
        app = make_app(use_app1)
        loader = app.Loader()
        data = loader.get_data()
        transformer = app.Transformer()
        transformer.transform_data(data=data)
    

© www.soinside.com 2019 - 2024. All rights reserved.