heapq.heapify不适用于子类列表

时间:2012-12-18 00:12:52

标签: python list heap subclass

我希望每次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如果重要

3 个答案:

答案 0 :(得分:4)

作为Python 3.0项目的一部分,再次针对3.3,他们浏览了文档,当某些内容需要list与一般sequence typemutable sequence typeiterableheapq肯定在3.3中说list,这意味着在2.7中也是如此。

如果您追查代码,如果您有C实现,则在_heapqmodule.c中,heapify显式调用PyList_Check以验证该类型是否为真实list而不是像list那样的序列。这不会捕获list的子类,但您可以看到它直接调用PyList_GETSIZE和(_siftup})PyList_GET_ITEMPyList_SET_ITEM,所以它会将list子类视为基础list对象。 (而且当前的主干没有改变。)

所以,有几种解决方法。

首先,正如@FogleBird建议的那样,你可以只分叉heapq的纯Python实现 - 只需将完全相同的内容复制到项目中,给它一个不同的名称,然后删除from _heapq import *位于318-321行。

然而,这可能会慢得多。

从CPython切换到PyPy可以自动解决这个问题(这也意味着无论你是否愿意,你都可以获得纯Python实现)。

事实上,我使用1,000,000项目列表进行了快速测试。在验证PyPy确实使用List2类之后,我对其进行了修改,以便将字符串存储到全局变量中,而不是打印。 (否则,打印时间比Mac上的实际工作长3倍,在Windows上长40倍......)然后我用各种不同的蟒蛇运行它:

  • CPython 2.7.2 64位Mac:2.079s
  • CPython 3.3.0 64位Mac:1.997s
  • CPython 3.3.0 32位Mac:2.197s
  • PyPy 2.7.2 / 1.9.0 64位Mac:1.619s

  • CPython 2.7.3 32位胜利:3.997s

  • PyPy 2.7.21.9.0 32位Win:2.334s

所以,尽管实际上调用了我的Python列表覆盖,但PyPy还是吹走了其他所有内容。 (我没有测试Jython或IronPython - 部分是因为JVM或.NET的启动和预热时间太长,以至于你需要更长时间的测试才能完全公平......但是他们也必须使用纯Python { {1}}模块。)

但这可能是一个比你想做的更戏剧性的改变。另一种方法是分叉heapq。即使您根本不了解C API,这实际上只是一个搜索和替换工作。对于每个_heapqmodule.c函数,请将其替换为相应的PySequence_Foo函数(PyList_FOO - > PyList_SIZEPySequence_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

想法是简单地将条目标记为已删除。当您在优先级队列的顶部看到这些项时,您会忽略它们。该文档包含示例代码。