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

Python:创建一个函数,通过引用而不是值来修改列表,python,list,Python,List,我正在做一些对性能至关重要的Python工作,并希望创建一个函数,如果满足某些条件,可以从列表中删除一些元素。我不想创建列表的任何副本,因为它充满了很多非常大的对象 我要实现的功能: def listCleanup(listOfElements): i = 0 for element in listOfElements: if(element.meetsCriteria()): del(listOfElements[i])

我正在做一些对性能至关重要的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是通过值传递还是通过引用传递

我怎样才能使它更快

是否可以以某种方式扩展list类并在其中实现listCleanup()

myList = range(10000)
myList.listCleanup()
谢谢-


Jonathan

在Python中,列表总是通过引用传递

列表中对象的大小不会影响列表的性能,因为列表只存储对对象的引用。但是,列表中的项目数确实会影响某些操作的性能,例如删除一个元素,即O(n)

如前所述,listCleanup是最坏的O(n**2),因为在一个可能是O(n)本身的循环中有O(n)del操作

如果元素的顺序无关紧要,则可以使用内置的
set
类型而不是列表。
集合
有O(1)个删除和插入。但是,您必须确保对象是不可变和可散列的

否则,最好重新创建列表。这就是O(n),您的算法需要至少是O(n),因为您需要检查每个元素。您可以在一行中筛选列表,如下所示:

listOfElements[:] = [el for el in listOfElements if el.MeetsCriteria()]

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\u foo
都会引用相同的对象。(Python从不隐式复制任何对象。)



您已建议对此操作存在性能问题。重复使用旧列表中的
del
语句不会导致代码不那么惯用,处理起来也更容易混淆,但会引入二次性能,因为每次都必须重新排列整个列表

为解决绩效问题:

  • 启动并运行它。除非代码正常工作,否则无法了解性能。这也会告诉你,你必须优化的是速度还是空间;您在代码中提到了这两个方面的问题,但优化常常涉及到以牺牲另一个为代价来获取其中一个

  • 配置文件。您可以使用该配置文件及时执行。有各种各样的第三方内存分析器,它们可能有些有用,但使用起来不太方便

  • 测量。或在进行更改时重新归档内存,以查看更改是否会带来改进,如果会,改进是什么

  • 为了使代码对内存更加敏感,您通常需要在存储数据的方式上进行范式转换,而不是像不构建第二个列表进行过滤这样的微优化。(时间也是如此:换一种更好的算法几乎总能获得最好的加速。然而,速度优化很难一概而论)

    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
    ,尤其是数值数学序列。这还可以加速执行大量向量操作的代码

  • 将数据存储到磁盘,如数据库中

需要澄清的是:

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)

看起来像是过早优化。在尝试优化之前,您应该尝试更好地了解python的工作原理


在这种特殊情况下,您不需要担心对象大小。复制列表是使用列表理解或切片,只执行表面复制(复制对对象的引用,即使该术语在python中并不适用)。但是列表中的项目数可能很重要,因为del是O(n)。可能还有其他解决方案,比如用None或传统的空对象替换一个项,或者使用另一种数据结构(如集合或字典),删除项的成本要低得多。

在迭代过程中修改数据结构就像是在砸自己的脚。。。迭代失败。你不妨采纳别人的建议,只需列出一个新的清单:

myList = [element for element in listOfElements if not element.meetsCriteria()]
旧列表(如果没有其他引用)将被释放,内存将被回收。更好的是,甚至不要复制列表。将上述内容更改为生成器表达式,以获得更便于记忆的版本:

myList = (element for element in listOfElements if not element.meetsCriteria())
所有Python对象访问都是通过引用进行的。对象被创建,变量只是对这些对象的引用。然而,如果有人想问一个纯粹的问题,“Python使用哪种类型的调用语义,按引用调用还是按值调用?”答案必须是“两者都不是……都是”。原因是调用约定对Python来说不如对象类型重要

如果一个对象是可变的,它可以被修改,不管你在什么范围内。。。只要有有效的对象引用,就可以更改对象。如果对象是不可变的,那么
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]