Как использовать универсальную типизацию с подклассом PyQt без конфликтов метаклассов? - PullRequest
0 голосов
/ 11 февраля 2019

Я пробовал abc.ABCMeta с типом sip-обертки, и он хорошо работает, когда подкласс с abc.ABC.

class QABCMeta(wrappertype, ABCMeta):
    pass

class WidgetBase(QWidget, metaclass=QABCMeta):
    ...

class InterfaceWidget(WidgetBase, ABC):
    ...

class MainWidget(InterfaceWidget):
    ...

Но он не работает на typing.Generic.

class QGenericMeta(wrappertype, GenericMeta):
    pass

class WidgetBase(QWidget, Generic[T], metaclass=QGenericMeta):
    ...

class GenericWidget(WidgetBase[float]):
    ...

Он поднял:

line 980, in __new__
    self if not origin else origin._gorg)
TypeError: can't apply this __setattr__ to sip.wrappertype object

Я ожидал, что он будет использовать общий подкласс как обычно:

class TableBase(QTableWidget, Generic[T]):
    @abstractmethod
    def raw_item(self, row: int) -> T:
        ...
    def data(self) -> Iterator[T]:
        yield from (self.raw_item(row) for row in range(self.rowCount()))

class MainTable(TableBase[float]):
    def raw_item(self, row: int) -> float:
        return float(self.item(row, 1).text())  # implementation

table = MainTable()
for data in table.data():
    data: float

Но data все еще Any, когда без наследования Generic[T].

Можно ли решить с помощью PEP 560 проверку типов?

1 Ответ

0 голосов
/ 22 февраля 2019

Ну, я нашел ответ.

Поскольку метакласс typing.Generic равен abc.ABC, он должен также основываться на abc.ABCMeta.Но это работает только с Python 3.7 или выше.

И затем, просто используйте type(QObject) вместо sip.wrappertype:

# -*- coding: utf-8 -*-

from abc import abstractmethod, ABC, ABCMeta
from typing import TypeVar, Generic, Iterator
from PyQt5.QtCore import QObject
from PyQt5.QtWidgets import QTableWidget

QObjectType = type(QObject)
T = TypeVar('T')


class QABCMeta(QObjectType, ABCMeta):
    pass


class BaseWidget(QTableWidget, Generic[T], metaclass=QABCMeta):

    @abstractmethod
    def raw_item(self, row: int) -> T:
        ...

    def data(self) -> Iterator[T]:
        yield from (self.raw_item(row) for row in range(self.rowCount()))


class TestWidget(BaseWidget[float], ABC):  # optional inherit ABC.

    def raw_item(self, row: int) -> float:
        return float(self.item(row, 1).text())


if __name__ == '__main__':
    w = TestWidget()
    for f in w.data():
        pass

Этот код работает для PyCharm IDE, аннотацияпеременной f является float.

При изменении PyQt5 на PySide2 это также работает!

...