是否需要范围(len (a)) ?

人们经常在 SO 上的 python 问题中找到这种类型的表达式。或者仅用于访问可迭代文件的所有项

for i in range(len(a)):
print(a[i])

这只是一种笨拙的写作方式:

for e in a:
print(e)

或者为迭代的元素赋值:

for i in range(len(a)):
a[i] = a[i] * 2

应该是相同的:

for i, e in enumerate(a):
a[i] = e * 2
# Or if it isn't too expensive to create a new iterable
a = [e * 2 for e in a]

或者过滤掉指数:

for i in range(len(a)):
if i % 2 == 1: continue
print(a[i])

可以这样表达:

for e in a [::2]:
print(e)

或者当你只需要列表的长度,而不是它的内容:

for _ in range(len(a)):
doSomethingUnrelatedToA()

可能是:

for _ in a:
doSomethingUnrelatedToA()

In python we have enumerate, slicing, filter, sorted, etc... As python for constructs are intended to iterate over iterables and not only ranges of integers, are there real-world use-cases where you need in range(len(a))?

292291 次浏览

Short answer: mathematically speaking, no, in practical terms, yes, for example for Intentional Programming.

Technically, the answer would be "no, it's not needed" because it's expressible using other constructs. But in practice, I use for i in range(len(a) (or for _ in range(len(a)) if I don't need the index) to make it explicit that I want to iterate as many times as there are items in a sequence without needing to use the items in the sequence for anything.

So: "Is there a need?"? — yes, I need it to express the meaning/intent of the code for readability purposes.

See also: https://en.wikipedia.org/wiki/Intentional_programming

And obviously, if there is no collection that is associated with the iteration at all, for ... in range(len(N)) is the only option, so as to not resort to i = 0; while i < N; i += 1 ...

If you need to work with indices of a sequence, then yes - you use it... eg for the equivalent of numpy.argsort...:

>>> a = [6, 3, 1, 2, 5, 4]
>>> sorted(range(len(a)), key=a.__getitem__)
[2, 3, 1, 5, 4, 0]

Going by the comments as well as personal experience, I say no, there is no need for range(len(a)). Everything you can do with range(len(a)) can be done in another (usually far more efficient) way.

You gave many examples in your post, so I won't repeat them here. Instead, I will give an example for those who say "What if I want just the length of a, not the items?". This is one of the only times you might consider using range(len(a)). However, even this can be done like so:

>>> a = [1, 2, 3, 4]
>>> for _ in a:
...     print True
...
True
True
True
True
>>>

Clements answer (as shown by Allik) can also be reworked to remove range(len(a)):

>>> a = [6, 3, 1, 2, 5, 4]
>>> sorted(range(len(a)), key=a.__getitem__)
[2, 3, 1, 5, 4, 0]
>>> # Note however that, in this case, range(len(a)) is more efficient.
>>> [x for x, _ in sorted(enumerate(a), key=lambda i: i[1])]
[2, 3, 1, 5, 4, 0]
>>>

So, in conclusion, range(len(a)) is not needed. Its only upside is readability (its intention is clear). But that is just preference and code style.

Sometimes matplotlib requires range(len(y)), e.g., while y=array([1,2,5,6]), plot(y) works fine, scatter(y) does not. One has to write scatter(range(len(y)),y). (Personally, I think this is a bug in scatter; plot and its friends scatter and stem should use the same calling sequences as much as possible.)

I have an use case I don't believe any of your examples cover.

boxes = [b1, b2, b3]
items = [i1, i2, i3, i4, i5]
for j in range(len(boxes)):
boxes[j].putitemin(items[j])

I'm relatively new to python though so happy to learn a more elegant approach.

Very simple example:

def loadById(self, id):
if id in range(len(self.itemList)):
self.load(self.itemList[id])

I can't think of a solution that does not use the range-len composition quickly.

But probably instead this should be done with try .. except to stay pythonic i guess..

It's nice to have when you need to use the index for some kind of manipulation and having the current element doesn't suffice. Take for instance a binary tree that's stored in an array. If you have a method that asks you to return a list of tuples that contains each nodes direct children then you need the index.

#0 -> 1,2 : 1 -> 3,4 : 2 -> 5,6 : 3 -> 7,8 ...
nodes = [0,1,2,3,4,5,6,7,8,9,10]
children = []
for i in range(len(nodes)):
leftNode = None
rightNode = None
if i*2 + 1 < len(nodes):
leftNode = nodes[i*2 + 1]
if i*2 + 2 < len(nodes):
rightNode = nodes[i*2 + 2]
children.append((leftNode,rightNode))
return children

Of course if the element you're working on is an object, you can just call a get children method. But yea, you only really need the index if you're doing some sort of manipulation.

What if you need to access two elements of the list simultaneously?

for i in range(len(a[0:-1])):
something_new[i] = a[i] * a[i+1]

You can use this, but it's probably less clear:

for i, _ in enumerate(a[0:-1]):
something_new[i] = a[i] * a[i+1]

Personally I'm not 100% happy with either!

If you have to iterate over the first len(a) items of an object b (that is larger than a), you should probably use range(len(a)):

for i in range(len(a)):
do_something_with(b[i])

Sometimes, you really don't care about the collection itself. For instance, creating a simple model fit line to compare an "approximation" with the raw data:

fib_raw = [1, 1, 2, 3, 5, 8, 13, 21] # Fibonacci numbers


phi = (1 + sqrt(5)) / 2
phi2 = (1 - sqrt(5)) / 2


def fib_approx(n): return (phi**n - phi2**n) / sqrt(5)


x = range(len(data))
y = [fib_approx(n) for n in x]


# Now plot to compare fib_raw and y
# Compare error, etc

In this case, the values of the Fibonacci sequence itself were irrelevant. All we needed here was the size of the input sequence we were comparing with.

My code is:

s=["9"]*int(input())
for I in range(len(s)):
while not set(s[I])<=set('01'):s[i]=input(i)
print(bin(sum([int(x,2)for x in s]))[2:])

It is a binary adder but I don't think the range len or the inside can be replaced to make it smaller/better.

One problem with for i, num in enumerate(a) is that num does not change when you change a[i]. For example, this loop:

for i, num in enumerate(a):
while num > 0:
a[i] -= 1

will never end. Of course, you could still use enumerate while swapping each use of num for a[i], but that kind of defeats the whole purpose of enumerate, so using for i in range(len(a)) just becomes more logical and readable.

I think it's useful for tqdm if you have a large loop and you want to track progress. This will output a progress bar:

from tqdm import tqdm


empty_list = np.full(len(items), np.nan)
for i in tqdm(range(len(items))):
empty_list[i] = do_something(items[i])

This will not show progress, at least in the case I was using it for:

empty_list = np.full(len(items), np.nan)
for i, _ in tqdm(enumerate(items)):
empty_list[i] = do_something(items[i])

Just showed number of iterations. Not as helpful.

Having a range of indices is useful for some more sophisticated problems in combinatorics. For example, to get all possible partitions of a list into three non-empty sections, the most straightforward approach is to find all possible combinations of distinct endpoints between the first and second section and between the second and third section. This is equivalent to ordered pairs of integers chosen from the valid indices into the list (except zero, since that would make the first partition empty). Thus:

>>> from itertools import combinations
>>> def three_parts(sequence):
...     for i, j in combinations(range(1, len(sequence)), 2):
...         yield (sequence[:i], sequence[i:j], sequence[j:])
...
>>> list(three_parts('example'))
[('e', 'x', 'ample'), ('e', 'xa', 'mple'), ('e', 'xam', 'ple'), ('e', 'xamp', 'le'), ('e', 'xampl', 'e'), ('ex', 'a', 'mple'), ('ex', 'am', 'ple'), ('ex', 'amp', 'le'), ('ex', 'ampl', 'e'), ('exa', 'm', 'ple'), ('exa', 'mp', 'le'), ('exa', 'mpl', 'e'), ('exam', 'p', 'le'), ('exam', 'pl', 'e'), ('examp', 'l', 'e')]