我正在做一些性能关键的Python工作,并希望创建一个函数,如果符合某些条件,则从列表中删除一些元素。我宁愿不创建列表的任何副本,因为它充满了很多非常大的对象。
我想实现的功能:
def listCleanup(listOfElements):
i = 0
for element in listOfElements:
if(element.meetsCriteria()):
del(listOfElements[i])
i += 1
return listOfElements
myList = range(10000)
myList = listCleanup(listOfElements)
我不熟悉Python的低级工作。 myList是通过值还是通过引用传递的?
如何让它更快?
是否有可能以某种方式扩展列表类并在其中实现listCleanup()?
myList = range(10000)
myList.listCleanup()
谢谢 -
乔纳森
答案 0 :(得分:29)
Python以相同的方式传递所有内容,但是“按值”或“按引用”调用它不会清除所有内容,因为Python的语义与这些术语通常适用的语言不同。如果我要描述它,我会说所有传递都是按值,并且该值是对象引用。 (这就是我不想说的原因!)
如果要从列表中过滤掉一些内容,可以构建一个新列表
foo = range(100000)
new_foo = []
for item in foo:
if item % 3 != 0: # Things divisble by 3 don't get through
new_foo.append(item)
或使用列表推导语法
new_foo = [item for item in foo if item % 3 != 0]
Python不会复制列表中的对象,而是foo
和new_foo
都会引用相同的对象。 (Python从不隐式复制任何对象。)
您已建议您对此操作有性能问题。使用旧列表中的重复del
语句将导致代码不再惯用且更难以处理,但它将引入二次性能,因为每次必须重新整理整个列表。
解决绩效问题:
启动并运行。除非您的代码正常运行,否则无法弄清楚您的性能是什么样的。这也将告诉您是否必须优化速度或空间;你在代码中提到了对这两者的担忧,但通常优化涉及以另一方为代价获得一个。
个人资料。您可以及时使用the stdlib tools来提高效果。有各种第三方内存分析器可能有点用,但不太适合使用。
测量。 Time或重新编制内存,当您进行更改以查看更改是否有所改进时,如果是,那么改进是什么。
为了使您的代码对内存更敏感,您通常需要在存储数据的方式上进行范式转换,而不是像不构建第二个列表进行过滤那样的微观优化。 (对于时间也是如此,真的:改为更好的算法几乎总能提供最好的加速。但是,更难以概括速度优化)。
在Python中优化内存消耗的一些常见范例转换包括
使用生成器。生成器是懒惰的迭代:它们不会立即将整个列表加载到内存中,它们会在运行中找出它们的下一个项目。要使用生成器,上面的代码段看起来像
foo = xrange(100000) # Like generators, xrange is lazy
def filter_divisible_by_three(iterable):
for item in foo:
if item % 3 != 0:
yield item
new_foo = filter_divisible_by_three(foo)
或使用生成器表达式语法
new_foo = (item for item in foo if item % 3 != 0)
将numpy
用于同源序列,特别是那些数字化的序列。这也可以加速执行大量矢量操作的代码。
将数据存储到磁盘,例如数据库中。
答案 1 :(得分:6)
在Python中,列表总是通过引用传递。
列表中对象的大小不会影响列表性能,因为列表仅存储对对象的引用。但是,列表中的项目数确实会影响某些操作的性能 - 例如删除元素,即O(n)。
如上所述,listCleanup是最坏情况的O(n ** 2),因为你在循环中有O(n)del操作,这个循环可能是O(n)本身。
如果元素的顺序无关紧要,您可以使用内置的set
类型而不是列表。 set
具有O(1)删除和插入。但是,您必须确保您的对象是不可变的和可清除的。
否则,最好重新创建列表。那是O(n),你的算法需要至少为O(n),因为你需要检查每个元素。您可以在一行中过滤列表,如下所示:
listOfElements[:] = [el for el in listOfElements if el.MeetsCriteria()]
答案 2 :(得分:2)
看起来像是过早优化。在尝试优化之前,您应该尝试更好地理解python的工作原理。
在这种特殊情况下,您无需担心对象大小。复制列表是使用列表推导或切片将只执行表面复制(复制对象的引用,即使该术语不适用于python)。但是列表中的项目数可能很重要,因为del是O(n)。可能还有其他解决方案,例如用None或传统Null对象替换项目,或使用其他数据结构(如集合或字典),其中删除项目的成本要低得多。
答案 3 :(得分:2)
我认为没有人提到实际使用过滤器。由于很多答案来自备受尊敬的人,我确信我是那个缺少某些东西的人。有人可以解释一下这会有什么问题:
new_list = filter(lambda o: o.meetsCriteria(), myList)
答案 4 :(得分:1)
在迭代它时修改你的数据结构就好像在脚下射击自己...迭代失败。你不妨拿别人的建议来做一个新的清单:
myList = [element for element in listOfElements if not element.meetsCriteria()]
旧列表 - 如果没有其他引用 - 将被解除分配并回收内存。更好的是,甚至不要复制清单。将上面的内容更改为生成器表达式以获得更加内存友好的版本:
myList = (element for element in listOfElements if not element.meetsCriteria())
所有Python对象访问都是通过引用。创建对象,变量只是对这些对象的引用。但是,如果有人想问纯粹问题,“Python使用什么类型的调用语义,按引用调用或按值调用?”答案必须是“既不......又两个。”原因是因为调用约定对于Python而言不如对象类型重要。
如果一个对象是可变的,无论你在哪个范围内都可以修改它......只要你有一个有效的对象引用,就可以改变对象。如果对象是不可变,则无论您身在何处或您拥有什么参考,都无法更改该对象。
答案 5 :(得分:1)
可以在原地删除列表元素,但不能在列表中前进。你的代码简单无效 - 随着列表的缩小,你可能会错过检查元素。你需要向后退,这样缩小的部分就在你身后,代码相当可怕。在我向您展示之前,有一些初步的考虑因素:
首先,垃圾是如何进入清单的?预防胜于治疗。
其次,列表中有多少元素,可能需要删除的百分比是多少?百分比越高,创建新列表的可能性就越大。
好的,如果您仍想在现场进行,请考虑一下:
def list_cleanup_fail(alist, is_bad):
i = 0
for element in alist:
print "i=%d alist=%r alist[i]=%d element=%d" % (i, alist, alist[i], element)
if is_bad(element):
del alist[i]
i += 1
def list_cleanup_ok(alist, is_bad):
for i in xrange(len(alist) - 1, -1, -1):
print "i=%d alist=%r alist[i]=%d" % (i, alist, alist[i])
if is_bad(alist[i]):
del alist[i]
def is_not_mult_of_3(x):
return x % 3 != 0
for func in (list_cleanup_fail, list_cleanup_ok):
print
print func.__name__
mylist = range(11)
func(mylist, is_not_mult_of_3)
print "result", mylist
这是输出:
list_cleanup_fail
i=0 alist=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] alist[i]=0 element=0
i=1 alist=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] alist[i]=1 element=1
i=2 alist=[0, 2, 3, 4, 5, 6, 7, 8, 9, 10] alist[i]=3 element=3
i=3 alist=[0, 2, 3, 4, 5, 6, 7, 8, 9, 10] alist[i]=4 element=4
i=4 alist=[0, 2, 3, 5, 6, 7, 8, 9, 10] alist[i]=6 element=6
i=5 alist=[0, 2, 3, 5, 6, 7, 8, 9, 10] alist[i]=7 element=7
i=6 alist=[0, 2, 3, 5, 6, 8, 9, 10] alist[i]=9 element=9
i=7 alist=[0, 2, 3, 5, 6, 8, 9, 10] alist[i]=10 element=10
result [0, 2, 3, 5, 6, 8, 9]
list_cleanup_ok
i=10 alist=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] alist[i]=10
i=9 alist=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] alist[i]=9
i=8 alist=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] alist[i]=8
i=7 alist=[0, 1, 2, 3, 4, 5, 6, 7, 9] alist[i]=7
i=6 alist=[0, 1, 2, 3, 4, 5, 6, 9] alist[i]=6
i=5 alist=[0, 1, 2, 3, 4, 5, 6, 9] alist[i]=5
i=4 alist=[0, 1, 2, 3, 4, 6, 9] alist[i]=4
i=3 alist=[0, 1, 2, 3, 6, 9] alist[i]=3
i=2 alist=[0, 1, 2, 3, 6, 9] alist[i]=2
i=1 alist=[0, 1, 3, 6, 9] alist[i]=1
i=0 alist=[0, 3, 6, 9] alist[i]=0
result [0, 3, 6, 9]
答案 6 :(得分:0)
要明确:
def listCleanup(listOfElements):
i = 0
for element in listOfElements:
if(element.meetsCriteria()):
del(listOfElements[i])
i += 1
return listOfElements
myList = range(10000)
myList = listCleanup(listOfElements)
与
相同def listCleanup(listOfElements):
i = 0
for element in listOfElements:
if(element.meetsCriteria()):
del(listOfElements[i])
i += 1
myList = range(10000)
listCleanup(listOfElements)