12

我有一个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 的泛型类型?

4

2 回答 2

13

这里的问题是它queue.Queue实际上并不继承自typing.Generic,但它的类型化存根说它确实如此。在 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] (ie 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'sTqueue.Queue's联系起来

于 2018-02-01T02:51:20.737 回答
4

组合与继承(“has a”与“is a”)在这里可能非常有用,因为您可以准确指定想要输入的内容,而不是依赖于预期父类中的输入状态(这可能不是很好) .

下面是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)打字。尽管从代码的角度来看,您可能正在继承一个伟大的类,但这并不一定等同于继承伟大的(甚至是函数式的)类型定义。组合可以规避这种挫败感。

于 2020-03-19T17:32:48.323 回答