Python generi c с объединением - PullRequest
0 голосов
/ 29 мая 2020

У меня есть типы документов и страниц, содержащие части данных и метаданных. Они выглядят одинаково:

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()

Теперь я хотел бы провести рефакторинг этих двух типов, чтобы использовать общий тип c. Я так и сделал:

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. if type (part) == DataStruct: все время возвращает False. Во время выполнения тип (часть) является одним из: DocumentData, DocumentMeta, PageData, PageMeta. Я понимаю, что мне нужно сравнить тип (часть) с фактическим типом DataStruct. Как правильно определить тип среды выполнения DataStruct?
    В python руководстве по подсказкам написано: Во время выполнения isinstance (x, T) вызовет TypeError. В общем, isinstance () и issubclass () не должны использоваться с типами. Я считаю, что здесь та же проблема.

    Я могу использовать type (self). orig_bases [0]. args [0] для вывода DataStruct, но это концептуально неверно . Он получит первый аргумент generi c вместо DataStruct. Таким образом, если подпись базового класса MetaDataStruct изменится на class MergedStruct (Struct, Generic [MetaStruct, DataStruct]) (аргументы TypeVar заменены), вместо DataStruct будет получен MetaStruct.

  2. По какой-то причине, когда я попытался инициализировать Document (part = 1) , он прошел. На практике я ожидал, что код вызовет TypeError.

Ответы [ 2 ]

0 голосов
/ 29 мая 2020

Временно использовал такой раствор:

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()
0 голосов
/ 29 мая 2020

Python не выполняет проверку типов во время выполнения; вам нужно использовать инструмент анализа stati c, например 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, которая выглядит так:

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

Вышеупомянутые проверки типов правильно (вы получите ошибки mypy, если не реализуете методы _data_type и _meta_type правильно в подклассе) и сможете использовать методы класса для вызова соответствующих конструкторов во время выполнения.

...