我希望每次heapq.heapify
函数更改堆列表中的元素时都会收到回调通知(这是btw,需要跟踪列表中的对象以及它们的索引如何更改)。
我的计划是从list
继承并覆盖__setitem__
方法,我将跟踪列表中的更改。所以这是子类:
class List2(list):
def __setitem__(self, key, value):
print 'setitem: key=',key,' value=',value
list.__setitem__(self, key, value)
def __getitem__(self, key):
print 'getitem: key=',key
return list.__getitem__(self, key)
然后我创建一个List2
的实例并为它调用heapify:
h = List2([12, -3, 0, 5, 1, 7])
heapq.heapify(h)
问题是,未在__setitem__
内调用被覆盖的heapq.heapify
。看起来heapq.heapify
将List2的实例视为默认列表。
我想这与heapq.heapify
是内置函数这一事实有关,但我仍然没有得到它。
为什么未从__setitem__
调用被覆盖的heapq.heapify
?
这里有一件有趣的事情是,如果我将heapq的代码复制粘贴到我的本地模块中(因此它不再是内置函数),那么它按预期工作,我会调用List2.__settiem__
,但是它不适用于默认(内置)heapq
。
Python 2.7如果重要
答案 0 :(得分:4)
作为Python 3.0项目的一部分,再次针对3.3,他们浏览了文档,当某些内容需要list
与一般sequence type
或mutable sequence type
或iterable
和heapq
肯定在3.3中说list
,这意味着在2.7中也是如此。
如果您追查代码,如果您有C实现,则在_heapqmodule.c
中,heapify
显式调用PyList_Check
以验证该类型是否为真实list
而不是像list
那样的序列。这不会捕获list
的子类,但您可以看到它直接调用PyList_GETSIZE
和(_siftup
})PyList_GET_ITEM
和PyList_SET_ITEM
,所以它会将list
子类视为基础list
对象。 (而且当前的主干没有改变。)
所以,有几种解决方法。
首先,正如@FogleBird建议的那样,你可以只分叉heapq
的纯Python实现 - 只需将完全相同的内容复制到项目中,给它一个不同的名称,然后删除from _heapq import *
位于318-321行。
然而,这可能会慢得多。
从CPython切换到PyPy可以自动解决这个问题(这也意味着无论你是否愿意,你都可以获得纯Python实现)。
事实上,我使用1,000,000项目列表进行了快速测试。在验证PyPy确实使用List2
类之后,我对其进行了修改,以便将字符串存储到全局变量中,而不是打印。 (否则,打印时间比Mac上的实际工作长3倍,在Windows上长40倍......)然后我用各种不同的蟒蛇运行它:
PyPy 2.7.2 / 1.9.0 64位Mac:1.619s
CPython 2.7.3 32位胜利:3.997s
所以,尽管实际上调用了我的Python列表覆盖,但PyPy还是吹走了其他所有内容。 (我没有测试Jython或IronPython - 部分是因为JVM或.NET的启动和预热时间太长,以至于你需要更长时间的测试才能完全公平......但是他们也必须使用纯Python { {1}}模块。)
但这可能是一个比你想做的更戏剧性的改变。另一种方法是分叉heapq
。即使您根本不了解C API,这实际上只是一个搜索和替换工作。对于每个_heapqmodule.c
函数,请将其替换为相应的PySequence_Foo
函数(PyList_FOO
- > PyList_SIZE
,PySequence_Size
- > PyList_GETITEM
等)。并将它出现的位置替换为模块名称。而已。然后构建模块,让fork PySequence->GetItem
尝试myheapq.py
而不是import _myheapq
。这仍然不会像内置实现那么快,但只是因为它会多次调用您的import _heapq
和__getitem__
方法,这正是您想要的。
答案 1 :(得分:3)
heapq
可用, _heapq
会使用C实现。
当您将heapq
模块复制到本地软件包时,找不到_heapq
,而Python implementation
被使用,确实使用了__setitem__
和__getitem__
因为您可以在heap[pos] = heap[childpos]
中找到_siftup
之类的语句。
答案 2 :(得分:1)
heapq使用本机代码(如果在您的平台上可用),我认为这是问题,尽管我没有完全了解原因。
也许您可以采用不同的方法,并跟踪列表项的原始指标。
>>> n = [12, -3, 0, 5, 1, 7]
>>> m = [(v, i) for i, v in enumerate(x)]
>>> heapq.heapify(m)
>>> m
[(-3, 1), (1, 4), (0, 2), (5, 3), (12, 0), (7, 5)]
然后你可以在堆化后提取值和指标......
>>> values, indicies = zip(*m)
>>> values
(-3, 1, 0, 5, 12, 7)
>>> indicies
(1, 4, 2, 3, 0, 5)
编辑:我试图通过提供一个不是从列表派生的类的实例来“欺骗”heapq。它不起作用,它需要列表,大概是因为本机代码使用它作为性能原因的假设。
>>> class List(object):
... def __init__(self, data):
... self.data = data
... def __getitem__(self, key):
... print 'getitem', key
... return self.data[key]
... def __setitem__(self, key, value):
... print 'setitem', key, value
... self.data[key] = value
...
>>> x = List([12, -3, 0, 5, 1, 7])
>>> heapq.heapify(x)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: heap argument must be a list
编辑2 :请注意heapq.py中的此代码。这会覆盖Python实现。
# If available, use C implementation
try:
from _heapq import *
except ImportError:
pass
编辑3 :Python文档讨论了您的根本问题。即,“如果需要删除待处理任务,您如何找到它并将其从队列中删除?”
http://docs.python.org/2/library/heapq.html#priority-queue-implementation-notes
想法是简单地将条目标记为已删除。当您在优先级队列的顶部看到这些项时,您会忽略它们。该文档包含示例代码。