mypy:如何定义泛型子类

时间:2017-07-31 10:59:46

标签: python types mypy

我有一个queue.Queue的子类,如此:

class SetQueue(queue.Queue):
    """Queue which will allow a given object to be put once only.

    Objects are considered identical if hash(object) are identical.
    """

    def __init__(self, maxsize=0):
        """Initialise queue with maximum number of items.

        0 for infinite queue
        """
        super().__init__(maxsize)
        self.all_items = set()

    def _put(self):
        if item not in self.all_items:
            super()._put(item)
            self.all_items.add(item)

我正在尝试使用mypy进行静态类型检查。在这种情况下,SetQueue应该采用通用对象T.这是我到目前为止的尝试:

from typing import Generic, Iterable, Set, TypeVar

# Type for mypy generics
T = TypeVar('T')

class SetQueue(queue.Queue):
    """Queue which will allow a given object to be put once only.

    Objects are considered identical if hash(object) are identical.
    """

    def __init__(self, maxsize: int=0) -> None:
        """Initialise queue with maximum number of items.

        0 for infinite queue
        """
        super().__init__(maxsize)
        self.all_items = set()  # type: Set[T]

    def _put(self, item: T) -> None:
        if item not in self.all_items:
            super()._put(item)
            self.all_items.add(item)

mypy在类定义行上发出警告,说“缺少泛型类型的参数”。

我认为我需要一个Generic[T],但我所做的每一次尝试都会引发语法错误。文档中的所有示例都显示了Generic[T]的子类,但不是来自任何其他对象的子类。

有谁知道如何为SetQueue定义泛型类型?

2 个答案:

答案 0 :(得分:4)

在这里,合成vs继承(“有一个”与“是一个”)非常有用,因为您可以准确指定要输入的 而不是依赖于输入状态预期的父类(可能不是很好)。

下面是SetQueue的完整实现(根据问题),今天100%通过mypy --strict而没有任何问题(或黑客)。为了简洁起见,我删除了文档字符串。

from typing import Generic, TypeVar, Set, Optional
import queue

T = TypeVar('T')  # Generic for the item type in SetQueue

class SetQueue(Generic[T]):
    def __init__(self, maxsize: int=0) -> None:
        self._queue: queue.Queue[T] = queue.Queue(maxsize)
        self.all_items: Set[T] = set()

    def _put(self, item: T) -> None:
        if item not in self.all_items:
            self._queue.put(item)
            self.all_items.add(item)

    # 100% "inherited" methods (odd formatting is to condense passthrough boilerplate)
    def task_done(self)           -> None: return self._queue.task_done()
    def join(self)                -> None: return self._queue.join()
    def qsize(self)               -> int:  return self._queue.qsize()
    def empty(self)               -> bool: return self._queue.empty()
    def full(self)                -> bool: return self._queue.full()
    def put_nowait(self, item: T) -> None: return self.put(item)
    def get_nowait(self)          -> T:    return self.get()
    def get(self, block: bool = True, timeout: Optional[float] = None) -> T:
        return self._queue.get(block, timeout)
    def put(self, item: T, block: bool = True, timeout: Optional[float] = None) -> None:
        return self._queue.put(item, block, timeout)

尽管组合肯定比继承更冗长(因为它需要定义所有直通方法),但是代码的清晰度可能会更好。另外,您并不总是希望所有的父方法和组合都可以忽略它们。

像这样构成现在在今天尤为重要,因为python生态系统(包括python标准库)的当前键入状态并不是100%出色的。基本上有两个平行的世界:1)实际代码,和2)输入。尽管从代码角度来看,您可能正在继承一个很棒的类,但这并不一定等于继承很棒的(甚至是功能性的)类型定义。组成可以避免这种挫败感。

答案 1 :(得分:3)

此处的问题是,queue.Queue实际上并非从typing.Generic继承,但typeshed stubs for it表示确实如此。这是一个必要的邪恶,直到stdlib完全购买typing,如果有的话。因此,实际的queue.Queue没有typing.GenericMeta元类,它在运行时中为通用类提供了__getitem__能力:

例如,此代码类型 - 在mypy中检查正常,但在运行时失败:

from typing import Generic, Iterable, Set, TypeVar, TYPE_CHECKING
import queue

# Type for mypy generics
T = TypeVar('T')


class SetQueue(queue.Queue[T]):
    """Queue which will allow a given object to be put once only.

    Objects are considered identical if hash(object) are identical.
    """

    def __init__(self, maxsize: int=0) -> None:
        """Initialise queue with maximum number of items.

        0 for infinite queue
        """
        super().__init__(maxsize)
        self.all_items = set()  # type: Set[T]

    def _put(self, item: T) -> None:
        if item not in self.all_items:
            super()._put(item)
            self.all_items.add(item)


my_queue = queue.Queue()  # type: queue.Queue[int]
my_queue.put(1)
my_queue.put('foo')  # error

my_set_queue = SetQueue()  # type: SetQueue[int]
my_set_queue.put(1)
my_set_queue.put('foo')  # error

引发的错误为TypeError: 'type' object is not subscriptable,表示不支持queue.Queue[T](即queue.Queue.__getitem__)。

这里也是一个让它在运行时工作的黑客:

from typing import Generic, Iterable, Set, TypeVar, TYPE_CHECKING
import queue

# Type for mypy generics
T = TypeVar('T')

if TYPE_CHECKING:
    Queue = queue.Queue
else:
    class FakeGenericMeta(type):
        def __getitem__(self, item):
            return self

    class Queue(queue.Queue, metaclass=FakeGenericMeta):
        pass


class SetQueue(Queue[T]):
    """Queue which will allow a given object to be put once only.

    Objects are considered identical if hash(object) are identical.
    """

    def __init__(self, maxsize: int=0) -> None:
        """Initialise queue with maximum number of items.

        0 for infinite queue
        """
        super().__init__(maxsize)
        self.all_items = set()  # type: Set[T]

    def _put(self, item: T) -> None:
        if item not in self.all_items:
            super()._put(item)
            self.all_items.add(item)


my_queue = queue.Queue()  # type: queue.Queue[int]
my_queue.put(1)
my_queue.put('foo')  # error

my_set_queue = SetQueue()  # type: SetQueue[int]
my_set_queue.put(1)
my_set_queue.put('foo')  # error

可能有更好的方法来修补元类。我很想知道是否有人想出更优雅的解决方案。

编辑:我应该注意多重继承不起作用,因为class SetQueue(queue.Queue, Generic[T])无法将SetQueue Tqueue.Queue&#39; < / p>