Python:创建一个函数来通过引用而不是值来修改列表

时间:2010-05-05 01:26:28

标签: python list

我正在做一些性能关键的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()

谢谢 -

乔纳森

7 个答案:

答案 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不会复制列表中的对象,而是foonew_foo都会引用相同的对象。 (Python从不隐式复制任何对象。)


您已建议您对此操作有性能问题。使用旧列表中的重复del语句将导致代码不再惯用且更难以处理,但它将引入二次性能,因为每次必须重新整理整个列表。

解决绩效问题:

  • 启动并运行。除非您的代码正常运行,否则无法弄清楚您的性能是什么样的。这也将告诉您是否必须优化速度或空间;你在代码中提到了对这两者的担忧,但通常优化涉及以另一方为代价获得一个。

  • 个人资料。您可以及时使用the stdlib tools来提高效果。有各种第三方内存分析器可能有点用,但不太适合使用。

  • 测量。 Time或重新编制内存,当您进行更改以查看更改是否有所改进时,如果是,那么改进是什么。

  • 为了使您的代码对内存更敏感,您通常需要在存储数据的方式上进行范式转换,而不是像不构建第二个列表进行过滤那样的微观优化。 (对于时间也是如此,真的:改为更好的算法几乎总能提供最好的加速。但是,更难以概括速度优化)。

    在Python中优化内存消耗的一些常见范例转换包括

    1. 使用生成器。生成器是懒惰的迭代:它们不会立即将整个列表加载到内存中,它们会在运行中找出它们的下一个项目。要使用生成器,上面的代码段看起来像

      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)
      
    2. numpy用于同源序列,特别是那些数字化的序列。这也可以加速执行大量矢量操作的代码。

    3. 将数据存储到磁盘,例如数据库中。

答案 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)