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