Python通用与联合

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

我有一个文档和页面类型,都包含数据和元数据部分。它们看起来是一样的。

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

现在类型检查的问题很少了。

  1. 就会一直返回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被检索。

  2. 出于某种原因,当我试图初始化的时候。文档(part=1),它通过了。实际上,我希望代码会引发TypeError。

python generics types subclass type-hinting
1个回答
0
投票

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 子类中的方法),并且能够在运行时使用类方法调用相应的构造函数。


0
投票

临时,我使用了这个解决方案。

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()
© www.soinside.com 2019 - 2024. All rights reserved.