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的启动和预热时间太长,您需要更长的测试才能公平……但他们还必须使用纯Pythonheapq
模块。)
但这可能是一个比你想做的更戏剧性的变化。另一种选择是fork\u heapqmodule.c
。即使您根本不了解C API,这实际上只是一个搜索和替换工作。对于每个PyList\u FOO
函数,将其替换为相应的函数(PyList\u SIZE
->PySequence\u SIZE
,PyList\u GETITEM
->PySequence->GETITEM
,等等)。并在显示的两个位置替换模块名称。就这样。然后构建模块,让您的forkmyheapq.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