使用 class 层次结构和 mypy 进行静态类型测试
Using a class hierarchy and mypy for static type testing
在下面的示例代码中,我有一个简单的 class 层次结构:Thing->Bookmark
.
from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import datetime
import rdflib
import json
import dateutil.parser
########### Class Hierarchy ##############ß
@dataclass
class Thing:
ID: str
@dataclass
class Bookmark(Thing):
creator: str
description: str
url: str
created_on: datetime
########### Ports #################
class RepositoryInterface(ABC):
@abstractmethod
def save(self, data: Thing) -> str:
pass
@abstractmethod
def get_by_id(self, ID: str) -> Thing:
pass
########### Adaptors ###############
class FileBasedBookmarkStore(RepositoryInterface):
def save(self, data: Bookmark) -> str:
with open(data.ID, "w") as f:
dataJson = data.__dict__
dataJson["created_on"] = data.created_on.isoformat()
json.dump(data.__dict__, f)
return data.ID
def get_by_id(self, ID: str) -> Bookmark:
with open(ID, "r") as f:
jsonBookmark = json.load(f)
jsonBookmark["created_on"] = dateutil.parser.parse(jsonBookmark["created_on"])
return Bookmark(**jsonBookmark)
我的存储库有一个为 Thing
定义的抽象 class,然后我为 Bookmark
实现了一个特定版本。
mypy
声明:
>
"save" 的参数 1 与超类型 "RepositoryInterface" 不兼容;超类型将参数类型定义为 "Thing"
但是,get_by_id
签名的return类型没有问题。
这是怎么回事,我该如何修复类型检查?
在 github
上找到 this 后,我将摘要 class 中的类型签名更改为 Any
。
########### Ports #################
class RepositoryInterface(ABC):
@abstractmethod
def save(self, data: Any) -> str:
pass
@abstractmethod
def get_by_id(self, ID: str) -> Thing:
pass
大多数其他参考资料都说,由于 Liskov 替换原则,早期版本被正确标记为问题。
但是,因为这个方法是抽象的,需要一个具体的实现,所以这里 Any
对我来说似乎没问题。
泛型是你的朋友。
from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import datetime
from typing import TypeVar, Generic
import json
import dateutil.parser
########### Class Hierarchy ##############ß
@dataclass
class Thing:
ID: str
@dataclass
class Bookmark(Thing):
creator: str
description: str
url: str
created_on: datetime
T = TypeVar('T', bound=Thing)
########### Ports #################
class RepositoryInterface(ABC, Generic[T]):
@abstractmethod
def save(self, data: T) -> str:
pass
@abstractmethod
def get_by_id(self, ID: str) -> T:
pass
########### Adaptors ###############
class FileBasedBookmarkStore(RepositoryInterface[Bookmark]):
def save(self, data: Bookmark) -> str:
with open(data.ID, "w") as f:
dataJson = data.__dict__
dataJson["created_on"] = data.created_on.isoformat()
json.dump(data.__dict__, f)
return data.ID
def get_by_id(self, ID: str) -> Bookmark:
with open(ID, "r") as f:
jsonBookmark = json.load(f)
jsonBookmark["created_on"] = dateutil.parser.parse(jsonBookmark["created_on"])
return Bookmark(**jsonBookmark)
在下面的示例代码中,我有一个简单的 class 层次结构:Thing->Bookmark
.
from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import datetime
import rdflib
import json
import dateutil.parser
########### Class Hierarchy ##############ß
@dataclass
class Thing:
ID: str
@dataclass
class Bookmark(Thing):
creator: str
description: str
url: str
created_on: datetime
########### Ports #################
class RepositoryInterface(ABC):
@abstractmethod
def save(self, data: Thing) -> str:
pass
@abstractmethod
def get_by_id(self, ID: str) -> Thing:
pass
########### Adaptors ###############
class FileBasedBookmarkStore(RepositoryInterface):
def save(self, data: Bookmark) -> str:
with open(data.ID, "w") as f:
dataJson = data.__dict__
dataJson["created_on"] = data.created_on.isoformat()
json.dump(data.__dict__, f)
return data.ID
def get_by_id(self, ID: str) -> Bookmark:
with open(ID, "r") as f:
jsonBookmark = json.load(f)
jsonBookmark["created_on"] = dateutil.parser.parse(jsonBookmark["created_on"])
return Bookmark(**jsonBookmark)
我的存储库有一个为 Thing
定义的抽象 class,然后我为 Bookmark
实现了一个特定版本。
mypy
声明:
> "save" 的参数 1 与超类型 "RepositoryInterface" 不兼容;超类型将参数类型定义为 "Thing"
但是,get_by_id
签名的return类型没有问题。
这是怎么回事,我该如何修复类型检查?
在 github
上找到 this 后,我将摘要 class 中的类型签名更改为 Any
。
########### Ports #################
class RepositoryInterface(ABC):
@abstractmethod
def save(self, data: Any) -> str:
pass
@abstractmethod
def get_by_id(self, ID: str) -> Thing:
pass
大多数其他参考资料都说,由于 Liskov 替换原则,早期版本被正确标记为问题。
但是,因为这个方法是抽象的,需要一个具体的实现,所以这里 Any
对我来说似乎没问题。
泛型是你的朋友。
from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import datetime
from typing import TypeVar, Generic
import json
import dateutil.parser
########### Class Hierarchy ##############ß
@dataclass
class Thing:
ID: str
@dataclass
class Bookmark(Thing):
creator: str
description: str
url: str
created_on: datetime
T = TypeVar('T', bound=Thing)
########### Ports #################
class RepositoryInterface(ABC, Generic[T]):
@abstractmethod
def save(self, data: T) -> str:
pass
@abstractmethod
def get_by_id(self, ID: str) -> T:
pass
########### Adaptors ###############
class FileBasedBookmarkStore(RepositoryInterface[Bookmark]):
def save(self, data: Bookmark) -> str:
with open(data.ID, "w") as f:
dataJson = data.__dict__
dataJson["created_on"] = data.created_on.isoformat()
json.dump(data.__dict__, f)
return data.ID
def get_by_id(self, ID: str) -> Bookmark:
with open(ID, "r") as f:
jsonBookmark = json.load(f)
jsonBookmark["created_on"] = dateutil.parser.parse(jsonBookmark["created_on"])
return Bookmark(**jsonBookmark)