Python heapq.heapify不适用于子类列表

Python heapq.heapify不适用于子类列表,python,list,heap,subclass,Python,List,Heap,Subclass,每次heapq.heapify函数更改堆列表中的元素时,我都希望收到回调通知(顺便说一句,这是跟踪列表中的对象及其索引如何更改所必需的) 我的计划是从列表中生成子类,并覆盖\uuuu setitem\uuuu方法,我将从该方法跟踪列表中的更改。下面是子类: 然后我创建一个List2的实例,并为其调用heapify: h = List2([12, -3, 0, 5, 1, 7]) heapq.heapify(h) 问题在于,未从heapq.heapify中调用被重写的\uuuuu setite

每次
heapq.heapify
函数更改堆列表中的元素时,我都希望收到回调通知(顺便说一句,这是跟踪列表中的对象及其索引如何更改所必需的)

我的计划是从
列表
中生成子类,并覆盖
\uuuu setitem\uuuu
方法,我将从该方法跟踪列表中的更改。下面是子类:

然后我创建一个
List2
的实例,并为其调用heapify:

h = List2([12, -3, 0, 5, 1, 7])
heapq.heapify(h)
问题在于,未从
heapq.heapify
中调用被重写的
\uuuuu setitem\uuuuuuuu
。它看起来像是
heapq.heapify
将List2的实例视为默认列表。 我想这与
heapq.heapify
是一个内置函数这一事实有关,但我还是不明白

为什么不从
heapq.heapify
调用被重写的
\uuuuuuu setitem\uuuuuuuuuuu

这里一件有趣的事情是,如果我将heapq的代码复制粘贴到我的本地模块中(因此它不再是一个内置函数),那么它就会按预期工作,我会收到对
List2.\uuu settiem\uuuu
的调用,但它不会与默认(内置)
heapq
一起工作


Python2.7如果重要的话

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)]
然后您可以提取heapify后的值和标记

>>> 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
编辑3:Python文档讨论了您的根本问题。也就是说,“如果需要删除挂起的任务,如何找到它并将其从队列中删除?”


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

heapq
如果可用,则使用C实现


当您将
heapq
模块复制到本地包中时,找不到
\u heapq
,并且使用了
Python实现
,它确实使用了
\uuuuuuuuu setitem\uuuuuu
\uu getitem\uuuuuuuuuu
,就像您可以找到的
heap[pos]=heap[childpos]这样的语句一样
作为Python 3.0项目的一部分,在
\siftup
中,以及在3.3中,他们仔细阅读了文档,使得当某些内容采用
列表时,与常规的
序列类型
可变序列类型
可变
相比,更加明确,并在3.3中明确地说
列表
,这意味着2.7中也是如此

如果跟踪代码,如果使用C实现,在中,
heapify
显式调用
PyList\u Check
,以验证类型是否为真实的
列表
,而不是类似于
列表的序列。这不会捕获
list
的子类,但您可以看到它直接调用
PyList\u GETSIZE
和(在
\u siftup
内)
PyList\u GET\u ITEM
PyList\u SET\u ITEM
,因此它将
list
子类视为基础
list
对象。(这在当前主干中没有更改。)

所以,有几种方法可以解决这个问题

首先,正如@FogleBird所建议的,您可以将的纯Python实现分叉-只需将完全相同的内容复制到您的项目中,给它一个不同的名称,并从第318-321行的_heapqimport*
位删除

然而,这可能要慢很多

从CPython切换到CPython可能会自动解决这个问题(这也意味着您将得到纯Python实现,无论您是否想要)

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

  • 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位Win:3.997s

  • PyPy 2.7.21.9.0 32位Win:2.334s
因此,PyPy吹走了其他一切,尽管实际上调用了我的Python列表覆盖。(我没有测试Jython或IronPython,部分原因是JVM或.NET的启动和预热时间太长,您需要更长的测试才能公平……但他们还必须使用纯Python
heapq
模块。)


但这可能是一个比你想做的更戏剧性的变化。另一种选择是fork
\u heapqmodule.c
。即使您根本不了解C API,这实际上只是一个搜索和替换工作。对于每个
PyList\u FOO
函数,将其替换为相应的函数(
PyList\u SIZE
->
PySequence\u SIZE
PyList\u GETITEM
->
PySequence->GETITEM
,等等)。并在显示的两个位置替换模块名称。就这样。然后构建模块,让您的fork
myheapq.py
尝试
import\u myheapq
而不是
import\u heapq
。这仍然不会像内置实现那样快,但这只是因为它会多次调用您的
\uuu getitem\uuuu
\uuuu setitem\uuuu
方法,这正是您想要的。

对不起,这不是我想要的。我想跟踪在多次heapify调用和const之后在堆列表中移动的对象
>>> 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
# If available, use C implementation
try:
    from _heapq import *
except ImportError:
    pass