python - 重写方法签名时如何避免违反里氏替换原则

标签 python python-3.x abstract-class mypy liskov-substitution-principle

我有一个抽象类DataWriter,它定义了一个抽象方法write()。该类应该是一组动态具体类的基类,其中每个类都旨在实现其自己的方法 write() 版本。为了定义方法write()的参数meta的数据类型,我创建了类型WriterMeta,如下所示:

WriterMeta = Typing.Union[GSheetWritable, S3Writable, LocalWritable]

每个具体类将负责处理符合联合的不同类型之一,但 linter mypy 似乎没有掌握这一点,因为当我定义方法的签名时使用联合类型之一作为参数 meta 的具体类的 write() ,它标志着违反 Liskov 替换原则,我对此相信不存在,因为具体类是抽象类的子集,这意味着父类可以毫无问题地替换子类。

这是我的代码:

class LocalWritable(typing.TypedDict):
    file_name: str


class GSheetWritable(typing.TypedDict):
    tab_name: str


class S3Writable(typing.TypedDict):
    data_name: str
    table_name: str


WriterMeta = typing.Union[GSheetWritable, S3Writable, LocalWritable]

class GSheetOutputWriter(DataWriter):
    def __init__(
        self, google_driver: GoogleApiDriver, folder: str, settings, timestamp, env
    ):
        self._connector = google_driver
        self.folder = folder
        self.settings = settings
        self.timestamp = timestamp
        self.env = env
        self.file_name = self.get_file_name()
        self._target = self.create_gsheet()
        self.new = True

    def get_file_name(self) -> str:
        file_name = (
            "boxes_shipping_costs_"
            + self.settings["volume_source"]
            + "_"
            + (
                self.timestamp
                if self.settings["volume_source"] == "adhoc"
                else self.settings["scm_week"]
            )
        )

        return file_name

    def create_gsheet(self):
        gsheet = self.connector.sheet_create(self.file_name, folder_id=self.folder)
        gsheet.worksheet("Sheet1").resize(rows=1, cols=1)

        return gsheet

    @property
    def connector(self) -> typing.Any:
        return self._connector

    @property
    def target(self) -> typing.Any:
        return self._target

    def write(self, data: pd.DataFrame, meta: GSheetWritable, versionize: bool):
        data = data.replace({np.nan: 0, np.Inf: "Inf"})

        print("Writing '{}' table to gsheet.".format(meta["tab_name"]))
        if self.new:
            tab = self.connector.get_worksheet(self.target.url, "Sheet1")
            self.connector.rename_worksheet(tab, meta["tab_name"])
            self.new = False
        else:
            tab = self.connector.add_worksheet(
                self.target, meta["tab_name"], rows=1, cols=1
            )

        time.sleep(random.randint(30, 60))
        self.connector.update_worksheet(
            tab, [data.columns.values.tolist()] + data.values.tolist()
        )

我对里氏替代原理的理解正确吗?如何重构这组类以使 mypy 接受它们?

最佳答案

为什么失败

当您声明对某种类型T(在您的情况下是Union)进行操作的抽象类方法时,从 mypy 的角度来看,它意味着以下内容:每个 子类必须实现此方法,该方法接受类型 T 或更宽的类型。仅接受 T 的一部分的方法违反了 LSP:每当您引用抽象类时,您都认为 T 类型的所有内容都可以在该方法中使用,而具体实现则不然不允许这样做。

类型安全方式 - 通用解决方案

您可以为此使用泛型类。

import pandas as pd
from abc import ABC, abstractmethod
from typing import Generic, TypeVar, TypedDict

class LocalWritable(TypedDict):
    file_name: str

class GSheetWritable(TypedDict):
    tab_name: str

class S3Writable(TypedDict):
    data_name: str
    table_name: str

# This variable is compatible with any of classes or their subclasses,
# not with `Union` of them as opposed to TypeVar(bound=Union[...]) usage.
_OutputT = TypeVar('_OutputT', GSheetWritable, S3Writable, LocalWritable)

class DataWriter(ABC, Generic[_OutputT]):
    @abstractmethod
    def write(self, data: pd.DataFrame, meta: _OutputT, versionize: bool) -> None: ...

class GSheetDataWriter(DataWriter[GSheetWritable]):
    def write(self, data: pd.DataFrame, meta: GSheetWritable, versionize: bool) -> None:
        pass
        # Implementation goes here

关于python - 重写方法签名时如何避免违反里氏替换原则,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71932334/

相关文章:

python-3.x - python SDK 上的 Azure Webjobs

grails - 我可以在Grails中为抽象域类自定义列名吗?

Python shell 在同一虚拟环境中同一台机器的不同位置上的工作方式不同

python - 对整个句子应用 NLP WordNetLemmatizer 显示错误且位置未知

Python Bot 按顺序发送消息

java - 如何将此父类(super class)更改为抽象类而不出现实例化异常?

objective-c - Objective-C 中的抽象类

python - 每行比较两列 Pandas 行

python - Django 2.1 的完整性错误

python - 如何使用 Windows 文件资源管理器使用 Python 选择并返回目录?