使用queue.PriorityQueue,不关心比较

时间:2019-01-03 18:28:47

标签: python priority-queue python-dataclasses

我正在尝试在Python 3(.6)中使用queue.PriorityQueue

我想存储具有给定优先级的对象。但是,如果两个对象具有相同的优先级,则我不介意PriorityQueue.get返回任何一个。换句话说,我的对象无法以整数进行比较,允许它们成为整数没有意义,我只关心优先级。

Python 3.7's documentation中,有一个涉及dataclasses的解决方案。我引用:

  

如果数据元素不具有可比性,则可以将数据包装在忽略数据项并且仅比较优先级数字的类中:

from dataclasses import dataclass, field
from typing import Any

@dataclass(order=True)
class PrioritizedItem:
    priority: int
    item: Any=field(compare=False)

A,我使用的是Python 3.6。在the documentation of this version of Python中,对于使用PriorityQueue设置优先级没有任何评论,也没有对“对象值”感到困扰,这在我的情况下是不合逻辑的。

是否有比在自定义类上定义__le__和其他比较方法更好的方法?我发现此解决方案特别丑陋且违反直觉,但这可能就是我。

4 个答案:

答案 0 :(得分:3)

dataclasses只是一种便捷的方法,可以避免创建大量样板代码。

您实际上没有创建一个类。具有唯一计数器值的元组也是如此:

from itertools import count

unique = count()

q.put((priority, next(unique), item))

,以使相等优先级之间的联系被后面的整数打破;因为它始终是唯一的,所以永远不会查询item值。

您还可以使用直接丰富的比较方法来创建类,该方法通过@functools.total_ordering变得更加简单:

from functools import total_ordering

@total_ordering
class PrioritizedItem:
    def __init__(self, priority, item):
        self.priority = priority
        self.item = item

    def __eq__(self, other):
        if not isinstance(other, __class__):
            return NotImplemented
        return self.priority == other.priority

    def __lt__(self, other):
        if not isinstance(other, __class__):
            return NotImplemented
        return self.priority < other.priority

答案 1 :(得分:1)

请参见priority queue implementation notes-在您引用的部分之前(关于使用dataclasses),它告诉您如何进行淘汰

  

...将条目存储为3元素列表,包括优先级,条目计数和任务。条目计数是一个平局,因此具有相同优先级的两个任务将按添加顺序返回。并且由于没有两个条目计数相同,所以元组比较将永远不会尝试直接比较两个任务。

因此,在添加到队列中时,只需将您的项作为 3rd 元素添加到元组(Prio, Count, YourElem)中即可。

有争议的示例:

from queue import PriorityQueue

class CompareError(ValueError): pass

class O:
    def __init__(self,n):
        self.n = n

    def __lq__(self):
        raise CompareError

    def __repr__(self): return str(self)
    def __str__(self): return self.n

def add(prioqueue,prio,item):
    """Adds the 'item' with 'prio' to the 'priorqueue' adding a unique value that
    is stored as member of this method 'add.n' which is incremented on each usage."""
    prioqueue.put( (prio, add.n, item))
    add.n += 1

# no len() on PrioQueue - we ensure our unique integer via method-param
# if you forget to declare this, you get an AttributeError
add.n = 0

h = PriorityQueue()

add(h, 7, O('release product'))
add(h, 1, O('write spec 3'))
add(h, 1, O('write spec 2'))
add(h, 1, O('write spec 1'))
add(h, 3, O('create tests'))

for _ in range(4):
    item = h.get()
    print(item)

使用h.put( (1, O('write spec 1')) )会导致

TypeError: '<' not supported between instances of 'O' and 'int'`

使用def add(prioqueue,prio,item):会推动三胞胎,因为要保证的项目具有明确的第二值,因此我们的O()实例永远不会被用作平局。

输出:

(1, 2, write spec 3)
(1, 3, write spec 2)
(1, 4, write spec 1)
(3, 5, create tests)

有关更好的唯一第二元素,请参见MartijnPieters答案@here

答案 2 :(得分:0)

让我们假设我们不想编写一个装饰器,其功能与dataclass相同。问题在于,我们不必定义所有比较运算符,以使我们的自定义类可根据优先级进行比较。 @functools.total_ordering装饰器可以提供帮助。摘录:

  

给定一个定义一个或多个丰富比较排序方法的类,该类装饰器提供其余的内容。这简化了指定所有可能的丰富比较操作所涉及的工作:

     

该类必须定义__lt__()__le__()__gt__()__ge__()中的一个。另外,该类应提供一个__eq__()方法。

使用提供的示例:

from functools import total_ordering

@total_ordering
class PrioritizedItem:
    # ...

    def __eq__(self, other):
        return self.priority == other.priority

    def __lt__(self, other):
        return self.priority < other.priority

答案 3 :(得分:0)

您所需要的只是一个包装类,该包装类实现__lt__以使PriorityQueue正常工作。记为here

  

在两个对象之间进行比较时,保证排序例程使用__lt__()。因此,很容易通过定义__lt__()方法向类添加标准排序顺序

就这么简单

class PriorityElem:
    def __init__(self, elem_to_wrap):
        self.wrapped_elem = elem_to_wrap

    def __lt__(self, other):
        return self.wrapped_elem.priority < other.wrapped_elem.priority

如果您的元素没有优先级,那就很简单:

class PriorityElem:
    def __init__(self, elem_to_wrap, priority):
        self.wrapped_elem = elem_to_wrap
        self.priority = other.priority

    def __lt__(self, other):
        return self.priority <  other.priority

现在您可以像这样使用PriorityQueue

queue = PriorityQueue()
queue.put(PriorityElem(my_custom_class1, 10))
queue.put(PriorityElem(my_custom_class2, 10))
queue.put(PriorityElem(my_custom_class3, 30))

first_returned_elem = queue.get()
# first_returned_elem is PriorityElem(my_custom_class1, 10)
second_returned_elem = queue.get()
# second_returned_elem is PriorityElem(my_custom_class2, 10)
third_returned_elem = queue.get()
# third_returned_elem is PriorityElem(my_custom_class3, 30)

在这种情况下,获取原始元素就像

一样简单
elem = queue.get().wrapped_elem

由于您不在乎排序稳定性,所以您只需要

编辑:如注释和confirmed here中所述,heappush不稳定:

  

与sorted()不同,此实现不稳定。