只需要索引:使用enumerate还是(x)range?

42

如果我只想在循环中使用索引,我是否应该更好地使用range/xrange函数与len()结合使用?

a = [1,2,3]
for i in xrange(len(a)):
    print i 

使用 enumerate 还是 p?即使我完全不会用到 p

for i,p in enumerate(a):
    print i    

8
我非常好奇你的使用场景是什么。 - Sven Marnach
我遇到了一些代码,其中实际上不应该在第一次使用enumerate [[profiel.attr[i].x for i,p in enumerate(profiel.attr)] for profiel in prof_obj]p是不需要的,或者应该是[[p.attr.x for p in profiel.attr] for profiel in prof_obj]。所以我问自己是否应该重写代码的某个地方... - LarsVegas
@Sven Marnach,最近我写了一些代码,实际上只需要索引来访问数组的切片,如下所示:sum_dist = [[sum(afst[:i]) for i,_ in enumerate(afst,start=1)] for afst in dist_betw]。(尽管我知道这个结构并不是必须的,因为我也可以使用itertools.accumulate() - LarsVegas
这是一种非常糟糕的计算累积和的方法。您一遍又一遍地对前面的元素求和,导致本质上是线性的东西具有二次复杂度。如果您已经使用Python 3.2,那么itertools.accumulate()是显而易见的选择。如果可以使用NumPy,则还可以使用numpy.cumsum()。在所有其他情况下,只需编写自己的O(n)cumsum()函数即可。 - Sven Marnach
我不知道它是如此糟糕。实际上,我一开始考虑定义一个cumsum函数,但后来有了这个想法。当然,你说的二次复杂度是正确的,但由于我处理的数据集非常小,所以这并没有让我感到可能会出问题。不过,还是谢谢你指出来给我。 - LarsVegas
显示剩余2条评论
8个回答

30

我会使用 enumerate,因为它更加通用——例如,它可以在可迭代对象和序列上正常工作,并且仅返回对对象的引用的开销并不是很大。而 xrange(len(something))(对我来说)虽然更容易读懂你的意图,但在不支持 len 的对象上会出错...


非常有趣的观点。哪些对象不支持 len() 的例子?函数? - LarsVegas
1
@larsvegas itertools.count(10) 是一个生成器。 - jamylak
2
请注意,itertools.count(10)是一个无限生成器,因此您也不想对其进行枚举。 - Sven Marnach
2
@SvenMarnach 好的,那么itertools.islice(itertools.count(10, 2), 50, 100)可能是一个更好的例子,尽管你也可以使用数学来创建一个从中得到xrange - jamylak
2
@jamylak:是的,或者使用iter([])更加简洁。 :) - Sven Marnach
返回对象的开销约为10-15%。如果要访问数据,请使用enumerate。如果只关心索引,则range / xrange将是更快的解决方案。 - ofer.sheffer

22

使用xrange和len结合的情况很常见,所以如果你只需要通过索引访问值,那么可以使用它。

但是,如果出于某些原因你更喜欢使用enumerate,你可以使用下划线(_),这只是一个经常看到的符号表示你不会以有意义的方式使用变量:

for i, _ in enumerate(a):
    print i

使用下划线(_)时也可能会遇到一个陷阱。在i18n库和系统中,将“翻译”函数命名为_是常见的做法,因此请小心在使用gettext或其他类似库时使用它(感谢@lazyr)。


2
请注意,不要将此习语与 gettext 结合使用,因为它会将 _ 变量用于其他用途,并且这种用法会遮蔽当前命名空间中的 gettext _。这可能会导致奇怪的错误。 - Lauritz V. Thaulow
2
@jamylak 不行 - Lauritz V. Thaulow
2
不使用“_”作为变量名的最重要原因是人们对它有各种奇怪的误解,往往会将其误认为某种特殊语法。我见过很多人因此感到困惑,所以我建议通过将其命名为“dummy”来避免这种混淆。显式优于隐式。 - Sven Marnach
1
@jamylak:我也会将未使用的名称更改为类似的形式。将其称为“dummy”的建议仅是因为人们往往会争论“_”可以清楚地表明变量是虚拟变量(这为什么会“清晰”他们心知肚明)。此外,一些IDE会警告未使用的变量,并且通常有一些名称模式被忽略,例如unused_xxx或类似模式。 - Sven Marnach
2
@jamylak。我对IDE的看法。Eclipse-PyDEV / Aptana默认情况下在查找未使用的名称时会忽略下划线(它不计入)。 - Rostyslav Dzinko
显示剩余5条评论

17

这是一个罕见的要求 - 容器中仅使用其长度信息!在这种情况下,我确实会明确这个事实并使用第一个版本。


4
应该会更快,但使用意味着当你意识到需要p时不需要更改。

3
如果你决定要这么做,这只是一个微不足道的变化,所以我不会仅仅因为这个去做决定。 - Mark Ransom

3

我进行了时间测试,发现 range 比 enumerate 快约两倍。(在Python 3.6 Win32平台上)

在长度为1M的情况下,取三次最佳结果:

  • enumerate(a): 0.125秒
  • range(len(a)): 0.058秒

希望对你有所帮助。

顺带一提,我最初开展这项测试是为了比较 Python 和 VBA 的速度...结果发现 VBA 的速度实际上比 range 方法快7倍...难道这是我 Python 技能不精的原因吗?

毫无疑问,Python 肯定可以以某种方式做得比 VBA 更好。

enumerate 脚本如下:

import time
a = [0]
a = a * 1000000
time.perf_counter()

for i,j in enumerate(a):
    pass

print(time.perf_counter())
import time
a = [0]
a = a * 1000000
time.perf_counter()

for i in range(len(a)):
    pass

print(time.perf_counter())

VBA脚本(0.008秒)

Sub timetest_for()
Dim a(1000000) As Byte
Dim i As Long
tproc = Timer
For i = 1 To UBound(a)
Next i
Debug.Print Timer - tproc
End Sub

2
我写这个是因为我想测试它。 所以要看你是否需要这些值来使用。
代码:
testlist = []
for i in range(10000):
    testlist.append(i)

def rangelist():
    a = 0
    for i in range(len(testlist)):
        a += i
        a = testlist[i] + 1   # Comment this line for example for testing

def enumlist():
    b = 0
    for i, x in enumerate(testlist):
        b += i
        b = x + 1   # Comment this line for example for testing

import timeit
t = timeit.Timer(lambda: rangelist())
print("range(len()):")
print(t.timeit(number=10000))
t = timeit.Timer(lambda: enumlist())
print("enum():")
print(t.timeit(number=10000))

现在你可以运行它,很可能会得到这样的结果,即 enum() 更快。 当你注释掉 a = testlist[i] + 1b = x + 1 这两行时,你会发现 range(len()) 更快。
对于上面的代码,我得到的结果是:
range(len()):
18.766527627612255
enum():
15.353173553868345

现在,按照上述说明进行评论时,我会得到以下内容:
range(len()):
8.231641875551514
enum():
9.974262515773656

我认为你应该添加一个帮助说明,说明这个测试展示了当你访问列表元素时,枚举(enumerate)更快,而当你不访问元素时,range(len)更快。 - ofer.sheffer

0

根据您的示例代码,

res = [[profiel.attr[i].x for i,p in enumerate(profiel.attr)] for profiel in prof_obj]

我会用另一个替换它

res = [[p.x for p in profiel.attr] for profiel in prof_obj]

-2

只需使用range()。如果你要使用所有的索引,xrange()没有任何实际的好处(除非len(a)非常大)。而enumerate()创建了一个更丰富的数据结构,但你会立即将其丢弃。


6
xrange()提供了非常大的好处!它不会在内存中创建临时列表,而是一个生成器。 - Rostyslav Dzinko
不需要这样做。OP只是在创建索引的范围。 - Rajesh J Advani
3
他正在遍历并逐个打印它们。 - jamylak
2
@RostyslavDzinko xrange不是生成器。它是一个序列对象,可以进行惰性求值。 - jamylak
实际上,迭代器是rangeiterator,它遍历xrange序列。例如,当您编写for i in xrange(3)时,它会调用iter(xrange(3))以获取rangeiterator - jamylak
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接