我有一个文档和页面类型,都包含数据和元数据部分。它们看起来是一样的。
class Document:
__data: DocumentData
__meta: DocumentMeta
def __init__(self, part: Union[DocumentData, DocumentMeta, None] = None, data: Optional[DocumentData] = None,
meta: Optional[DocumentMeta] = None):
super().__init__()
self.data: Optional[DocumentData] = data
self.meta: Optional[DocumentMeta] = meta
if part is not None:
if type(part) == DocumentData:
data = part
meta = DocumentMeta()
elif type(part) == DocumentMeta:
meta = part
data = DocumentData()
class Page:
__data: PageData
__meta: PageMeta
def __init__(self, part: Union[PageData, PageMeta, None] = None, data: Optional[PageData] = None,
meta: Optional[PageMeta] = None):
super().__init__()
self.data: Optional[PageData] = data
self.meta: Optional[PageMeta] = meta
if part is not None:
if type(part) == PageData:
data = part
meta = PageMeta()
elif type(part) == PageMeta:
meta = part
data = PageData()
我现在想重构这两个类型,使用一个通用类型。我是这样做的。
from typing import Generic, Optional, TypeVar, Union
DataStruct = TypeVar('DataStruct')
MetaStruct = TypeVar('MetaStruct')
class MetaDataStruct(Generic[DataStruct, MetaStruct]):
__data: DataStruct
__meta: MetaStruct
def __init__(
self,
part: Union[DataStruct, MetaStruct, None] = None,
data: Optional[DataStruct] = None,
meta: Optional[MetaStruct] = None
):
super().__init__()
self.data: Optional[DataStruct] = data
self.meta: Optional[MetaStruct] = meta
if part is not None:
if type(part) == DataStruct:
data = part
meta = MetaStruct()
elif type(part) == MetaStruct:
meta = part
data = DataStruct()
class DocumentData:
pass
class DocumentMeta:
pass
class PageData:
pass
class PageMeta:
pass
class Document(MetaDataStruct[DocumentData, DocumentMeta]):
pass
class Page(MetaDataStruct[PageData, PageMeta]):
pass
现在类型检查的问题很少了。
就会一直返回False 在运行时,一个type(part)是其中之一。DocumentData, DocumentMeta, PageData, PageMeta. 我知道我必须将type(part)和DataStruct的实际类型进行比较。正确的解决DataStruct的运行时类型的方法是什么?在 python提示手册 它的写法。在运行时,isinstance(x, T) 会引发TypeError. 一般来说,isinstance()和issubclass()不应该用于类型。 我相信这里也有同样的问题。
我可以使用 "isinstance() "和 "issubclass() "来解决这个问题。type(self).type(self).orig_bases[0].args[0] 来推断DataStruct,但这在概念上是错误的。它将检索第一个通用参数而不是DataStruct。所以,如果一个MetaDataStruct基类的签名将改为 Class MergedStruct(Struct, Generic[MetaStruct, DataStruct]) (交换了TypeVar参数),MetaStruct将代替DataStruct被检索。
出于某种原因,当我试图初始化的时候。文档(part=1),它通过了。实际上,我希望代码会引发TypeError。
Python 不会在运行时进行类型检查,你需要使用一个静态分析工具,如 mypy
. 在你给出的代码上运行mypy会显示这些错误。
22: error: 'DataStruct' is a type variable and only valid in type context
23: error: Incompatible types in assignment (expression has type "Union[DataStruct, MetaStruct]", variable has type "Optional[DataStruct]")
24: error: 'MetaStruct' is a type variable and only valid in type context
25: error: 'MetaStruct' is a type variable and only valid in type context
26: error: Incompatible types in assignment (expression has type "Union[DataStruct, MetaStruct]", variable has type "Optional[MetaStruct]")
27: error: 'DataStruct' is a type variable and only valid in type context
如果你添加一行试图初始化 Document(part=1)
你不会得到一个运行时错误(在你的代码中没有任何东西会引起一个错误,你的 if/elif
将只是一个无操作),但你会得到一个来自mypy的tyechecking错误,看起来像。
54: error: Argument "part" to "Document" has incompatible type "int"; expected "Union[DocumentData, DocumentMeta, None]"
问题是... type()
检查你想做的事情(并与相应的 isinstance
)是,一个 TypeVar
没有运行时值,所以你不能把它作为构造函数来调用。 请看 实例化一个TypeVar类型。
解决这个问题的一个方法是要求子类提供实际的类型。
from abc import ABC, abstractclassmethod
from typing import Generic, Optional, Type, TypeVar, Union
DataStruct = TypeVar('DataStruct')
MetaStruct = TypeVar('MetaStruct')
class MetaDataStruct(Generic[DataStruct, MetaStruct], ABC):
@abstractclassmethod
def _data_type(cls) -> Type[DataStruct]:
pass
@abstractclassmethod
def _meta_type(cls) -> Type[MetaStruct]:
pass
def __init__(
self,
part: Union[DataStruct, MetaStruct, None] = None,
data: Optional[DataStruct] = None,
meta: Optional[MetaStruct] = None
):
super().__init__()
self.data: Optional[DataStruct] = data
self.meta: Optional[MetaStruct] = meta
if part is not None:
if isinstance(part, self._data_type()):
data = part
meta = self._meta_type()()
elif isinstance(part, self._meta_type()):
meta = part
data = self._data_type()()
class DocumentData:
pass
class DocumentMeta:
pass
class Document(MetaDataStruct[DocumentData, DocumentMeta]):
@classmethod
def _data_type(cls) -> Type[DocumentData]:
return DocumentData
@classmethod
def _meta_type(cls) -> Type[DocumentMeta]:
return DocumentMeta
上面的typechecks是正确的(如果你没有实现这个类型,你会得到mypy错误) _data_type
和 _meta_type
子类中的方法),并且能够在运行时使用类方法调用相应的构造函数。
临时,我使用了这个解决方案。
actual_data_struct = getattr(type(self), '__orig_bases__')[0].__args__[0]
actual_meta_struct = getattr(type(self), '__orig_bases__')[0].__args__[1]
if part is not None:
if type(part) == actual_data_struct:
data = part
meta = actual_meta_struct()
elif type(part) == actual_meta_struct:
meta = part
data = actual_data_struct()