通过这里的一篇帖子,我读到使用next()来搜索和检索列表中第一次出现的元素可能很快。然而,我惊讶地看到传统的for-if-break语法在相当长的时间内表现更好。如果我在分析中犯了错误,请纠正我。 以下是我尝试的内容片段:
>>> def compare_2():
... device = 'a'
... l = ['a', 'b', 'c', 'd']
... z = next((device for x in l if x==device), None)
>>> def compare_1():
... device = 'a'
... l = ['a', 'b', 'c', 'd']
... z = None
... for x in l:
... if x == device:
... z = device
... break
>>> import timeit
>>> t = timeit.Timer(setup='from __main__ import compare_2', stmt='compare_2()')
>>> t.timeit()
1.5207240581512451
>>> t = timeit.Timer(setup='from __main__ import compare_1', stmt='compare_1()')
>>> t.timeit()
0.46623396873474121
我认为这可能会发生,因为我试图搜索和检索列表中的第一个元素作为示例。我也试过最后一个元素并注意到next()的表现并不比以前好。
>>> def compare_2():
... device = 'd'
... l = ['a', 'b', 'c', 'd']
... z = next((device for x in l if x==device), None)
...
>>>
>>> def compare_1():
... device = 'd'
... l = ['a', 'b', 'c', 'd']
... z = None
... for x in l:
... if x == device:
... z = device
... break
...
>>>
>>> t = timeit.Timer(setup='from __main__ import compare_2', stmt='compare_2()')
>>> t.timeit()
1.6903998851776123
>>> t = timeit.Timer(setup='from __main__ import compare_1', stmt='compare_1()')
>>> t.timeit()
0.66585493087768555
在优化代码方面,很想知道何时实际使用next(),何时不知道。 谢谢!
更新:
if device in l
肯定会更快。
我其实只想把一个简单的案例原型化。我在尝试根据属性匹配从对象列表中检索对象时遇到了这种情况。例如:
obj = next(如果obj.value == 1,obj_list中的obj为obj)
答案 0 :(得分:2)
我认为您的代码不公平。当你打电话给下一个时,你就是在里面创建一个生成器"从头开始"使用内置的python语法。当你在for循环中遍历列表时,for循环将调用列表的 iter 方法,该方法可能返回预先创建的迭代器,或者至少比生成器更有效地创建迭代器。我会重新尝试你手头创建发生器的时间,然后再打电话。另一种更公平地计时的方法可能是使用一个非常大的列表,这样,与遍历查找符合条件的第一个元素相比,创建生成器的开销会很小。
最后,我不认为你应该担心所有语言的python中的这种事情。尽可能写出最清晰的代码,不要过早优化。特别是在像python这样的语言中,优化的路径通常涉及调用用Cthon编写的库和python绑定(例如numpy)或者用C / C ++重写部分代码。
编辑:这里有一些源代码和结果
x = [1] * 1000000
x[500000] = 0
def func1(l):
...: for n in l:
...: if n == 0:
...: break
...:
def func2(l):
...: z = next((n for n in x if n == 0))
...:
%timeit func1(x)
100 loops, best of 3: 10.4 ms per loop
%timeit func2(x)
100 loops, best of 3: 10.4 ms per loop
接下来很好,但说实话,我并不认为我曾经在找到满足某些条件的第一个元素之外使用它。说实话,我是一位经验丰富的python程序员,熟悉这个成语,但如果我不熟悉那么我会发现for循环更清晰。
答案 1 :(得分:2)
next()
与生成器结合使用非常有用且快速。不幸的是,您一直使用普通列表作为生成器的源。如果你想充分发挥下一个潜力,你也必须使用发电机。
>>> timeit.timeit('next((i for i in range(1000)))')
10.571892976760864
>>> timeit.timeit('next((i for i in xrange(1000)))')
0.9348869323730469
元素越多,差异越大。使用第二个版本,Python不必处理所有列表,而只需处理第一个元素。
所以next()
并不快,但是当使用时,使用iteratabels和generator的概念就是这样。 next()
是为他们设计的。
答案 2 :(得分:1)
我想知道是否还会发生其他事情。创建生成器有一些开销,但我认为将条件if x==device
放在生成器中会强制它生成整个列表,并在next()运行之前创建一个新列表。
请参阅此示例,比较强制创建新列表的列表推导,以及懒惰并且不强制它的生成器:
>>> from timeit import Timer
>>> # List comprehension forces a new list to be created in memory
>>> def f1():
... q = [x for x in xrange(1000)]
... r = q[1]
... return r
...
>>> # Generator comprehension does 'lazy' iteration, only when needed
>>> def f2():
... q = (x for x in xrange(1000))
... r = next(q)
... return r
...
>>> Timer(f1).timeit()
47.420308774268435
>>> Timer(f2).timeit()
1.346566078497844
看到列表理解比较慢,而生成器惰性方法意味着它只在你调用next()时开始迭代,取值并停止。
现在这个例子,唯一的变化是两个都采用if x = 999
的最后一个元素:
>>> # List comprehension still forces creation of a new list
>>> # although the list only ends up with one element
>>> # nb. it's the last element
>>> def f1():
... q = [x for x in xrange(1000) if x == 999]
... r = q[0]
... return r
...
>>> # Generator comprehension is lazy
>>> # nb. it also only returns the last element
>>> def f2():
... q = (x for x in xrange(1000) if x == 999)
... r = next(q)
... return r
...
>>> Timer(f1).timeit()
37.279105355189984
>>> Timer(f2).timeit()
37.46816399778598
看他们现在基本相同。发电机正在减速。这个条件迫使它做了与列表理解相同的事情,在没有评估整个列表的情况下,它不能懒得只拿一个匹配的东西。
所以我认为在你的例子中,你不是只是看到创建一个生成器的开销,然后像其他人回答的那样调用它,正如我原来的评论所说的那样。
我认为通过包含if x==device
条件,您强制生成器构造迭代整个列表,和创建一个新的列表对象,和用所有结果填充它,然后在新列表上创建一个生成器,然后调用它来获得结果。
因此,在迭代现有列表的for循环中, lot 的开销更大,这不是因为next()
本质上很慢。
修改:您可以在生成器表达式添加到Python时在提案中看到它:PEP-0289 - Generator Expressions,在Early Binding vs Late Binding
部分中要求总结绑定第一个表达的推理,Guido提出[5]:
考虑
sum(x for x in foo())
。现在假设foo()中存在一个错误 引发异常,sum()中的一个错误引发了一个异常 在开始迭代其参数之前的异常。哪一个 您希望看到例外吗?如果是那个人,我会感到惊讶 sum()被引发而不是foo()中的那个,因为调用了foo() 是sum()的参数的一部分,我希望参数是 在调用函数之前处理。OTOH,
sum(bar(x) for x in foo())
,其中sum()和foo() 是无bug的,但bar()引发了异常,我们别无选择 延迟调用bar()直到sum()开始迭代 - 那就是 发电机合同的一部分。 (他们什么也没做 首先调用next()方法。)
换句话说,如果x==device
要提出异常,因为列表中的一个项目无法进行比较,例如来自自定义对象的类型错误,您可能希望在next()被调用之前看到该异常,因此强制整个列表被迭代,失去您可能希望看到的生成器延迟的保存,并创建更多列表与for循环相比,对象创建开销。