Python:创建一个通过引用而非值修改列表的函数

15 投票
7 回答
21084 浏览
提问于 2025-04-15 22:21

我正在做一些对性能要求很高的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 个回答

2

这看起来像是过早优化。你应该先更好地理解一下Python是怎么工作的,再去考虑优化。

在这个特定的情况下,你不需要担心对象的大小。复制一个列表时,使用列表推导式或者切片只会进行表面复制(其实这个说法在Python中并不完全适用,因为它只是复制了对象的引用)。不过,列表中的项目数量可能会很重要,因为使用del删除元素的时间复杂度是O(n),也就是说删除的时间会随着列表长度的增加而增加。还有其他解决方案,比如用None或者一个普通的空对象替换某个元素,或者使用其他数据结构,比如集合或字典,这样删除元素的成本会低很多。

6

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

列表中对象的大小不会影响列表的性能,因为列表只存储对象的引用。不过,列表中项目的数量会影响某些操作的性能,比如删除一个元素,这个操作的复杂度是O(n)。

按照目前的写法,listCleanup在最坏情况下是O(n²),因为在一个可能是O(n)的循环中,有一个O(n)的删除操作。

如果元素的顺序不重要,你可以考虑使用内置的set类型来代替列表。set的删除和插入操作都是O(1)。不过,你需要确保你的对象是不可变的并且可以哈希。

否则,你最好重新创建列表。这是O(n),而且你的算法至少需要是O(n),因为你需要检查每个元素。你可以用一行代码来过滤列表,像这样:

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

在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语句从旧列表中删除元素,会导致代码变得不太规范,也更难处理,同时还会引入性能问题,因为每次都需要重新排列整个列表。

为了提高性能:

  • 先让它运行起来。 你无法知道你的性能如何,除非你的代码能正常工作。这也会告诉你是速度更重要还是内存更重要;你在代码中提到对两者的担忧,但通常优化往往是为了一个而牺牲另一个。

  • 进行性能分析。 你可以使用标准库工具来分析时间性能。有一些第三方的内存分析工具也能帮忙,但使用起来可能没有那么方便。

  • 测量。 当你对代码进行修改时,可以使用timeit来测量时间或重新分析内存,看看修改是否带来了改善,以及改善的程度。

  • 为了让你的代码更节省内存,通常你需要在数据存储方式上进行一些思维转变,而不是微小的优化,比如不再创建第二个列表来进行过滤。(其实在时间上也是如此:换用更好的算法几乎总能带来最佳的速度提升。不过,关于速度优化的通用性更难以概括。)

    一些常见的思维转变来优化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. 将数据存储到磁盘上,比如放在数据库中。

撰写回答