HuL*_*iCa 2 python abstract-class liskov-substitution-principle python-3.x mypy
我有一个DataWriter定义抽象方法的抽象类write()。该类应该是一组动态具体类的基类,其中每个类都旨在实现其自己的方法版本write()。为了定义meta方法参数的数据类型,我创建了WriterMetawrite()类型,如下所示:
WriterMeta = typing.Union[GSheetWritable, S3Writable, LocalWritable]
每个具体类将负责处理符合联合的不同类型之一,但 linter mypy似乎没有掌握这一点,因为当我write()使用其中一种类型定义具体类的方法的签名时参数的联合meta,它标记了 a Liskov substituion principle violation,我认为它不存在,因为具体类是抽象类的子集,这意味着父类可以毫无问题地替换子类。
这是我的代码:
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()
)
Run Code Online (Sandbox Code Playgroud)
我对里氏替换原理的理解对吗?我如何重构这组类以使mypy接受它们?
当您声明对某种类型T(Union在您的情况下)进行操作的抽象类方法时,从 mypy 的角度来看,这意味着以下内容:每个子类都必须实现接受类型T或更宽类型的方法。方法,仅接受 的一部分T,违反了 LSP:每当您引用抽象类时,您都认为所有类型都T可以在该方法中使用,而具体实现不允许这样做。
您可以为此使用泛型类。
\nimport pandas as pd\nfrom abc import ABC, abstractmethod\nfrom typing import Generic, TypeVar, TypedDict\n\nclass LocalWritable(TypedDict):\n file_name: str\n\nclass GSheetWritable(TypedDict):\n tab_name: str\n\nclass S3Writable(TypedDict):\n data_name: str\n table_name: str\n\n# This variable is compatible with any of classes or their subclasses,\n# not with `Union` of them as opposed to TypeVar(bound=Union[...]) usage.\n_OutputT = TypeVar(\'_OutputT\', GSheetWritable, S3Writable, LocalWritable)\n\nclass DataWriter(ABC, Generic[_OutputT]):\n @abstractmethod\n def write(self, data: pd.DataFrame, meta: _OutputT, versionize: bool) -> None: ...\n\nclass GSheetDataWriter(DataWriter[GSheetWritable]):\n def write(self, data: pd.DataFrame, meta: GSheetWritable, versionize: bool) -> None:\n pass\n # Implementation goes here\nRun Code Online (Sandbox Code Playgroud)\n