192

I have some code like:

for i in range(N):
    do_something()

I want to do something N times. The code inside the loop doesn't depend on the value of i.

Is it possible to do this simple task without creating a useless index variable, or in an otherwise more elegant way? How?

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
Manuel Araoz
  • 15,962
  • 24
  • 71
  • 95
  • 7
    I just learned about the _ variable, but otherwise I would consider the way you're doing it Pythonic. I don't think I've ever seen a simple for loop done any other way, at least in python. Though I'm sure there are specific use cases where you look at it and say "Wait, that looks terrible" - but in general, xrange is the preferred way (as far as I've seen). – Wayne Werner Jun 04 '10 at 17:50
  • 8
    NOTE: xrange does not exist in Python3. Use `range` instead. – John Henckel Dec 07 '16 at 20:08

8 Answers8

129

A slightly faster approach than looping on xrange(N) is:

import itertools

for _ in itertools.repeat(None, N):
    do_something()
Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • 3
    How much faster? Is there still a difference in Python 3.1? – Hamish Grubijan Jun 04 '10 at 01:18
  • 17
    @Hamish: My test with 2.6 says 32% faster (23.2 us vs 17.6 us for N=1000). But that is a really time time anyways. I would default to the OP's code because it is more immediately readable (to me). – Mike Boers Jun 04 '10 at 01:31
  • 3
    That's good to know about the speed. I certainly echo Mike's sentiment about the OP's code being more readable. – Wayne Werner Jun 04 '10 at 17:52
  • @Wayne, I guess habit is really very powerful -- except for the fact that you're used to it, why else would "count up from 0 to N-1 [[and completely ignore the count]] each time performing this count-independent operation" be intrinsically any clearer than "repeat N times the following operation"...? – Alex Martelli Jun 04 '10 at 18:53
  • 5
    are you sure the speed is really relevant? Isn't it so that If you do anything significant in that loop, it will very likely take hundreds or thousands as much time as the iteration style you chose? – Henning May 05 '14 at 19:22
  • 1
    Apart from that, yes, it's somehow clearer as it uses a method named "repeat" to repeat something. But it's also a longer and rarer expression, and also adds an extra import - which also eats time and resources. I guess it's actually a very philosophical decision - basically the reason why I landed here, searching for an alternative to the above after just having written is in a piece of code. That said, I'm not 100% convinced to go and change the code now... – Henning May 05 '14 at 19:37
  • It completely misses the point because you're using a for loop and for loops' syntax require a collection and a variable that will contain one element of that collection at a time. But as OP said, he doesn't need that element, and so there's no point of using a for loop. TL;DR : we want to get rid of i, _, whatever. – ychaouche Jul 29 '14 at 18:09
66

Use the _ variable, like so:

# A long way to do integer exponentiation
num = 2
power = 3
product = 1
for _ in range(power):
    product *= num
print(product)
Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
GreenMatt
  • 18,244
  • 7
  • 53
  • 79
  • Worth noting that while this is a good stylistic choice, `_` is as real a variable as `i` and is still assigned a value on every loop iteration just as in the example in the question. – Grisha Levit Jun 10 '23 at 19:17
55

I just use for _ in range(n), it's straight to the point. It's going to generate the entire list for huge numbers in Python 2, but if you're using Python 3 it's not a problem.

11

since function is first-class citizen, you can write small wrapper (from Alex answers)

def repeat(f, N):
    for _ in itertools.repeat(None, N): f()

then you can pass function as argument.

Anycorn
  • 50,217
  • 42
  • 167
  • 261
  • @Hamish: Almost nothing. (17.8 us per loop under the same conditions as the timings for Alex's answer, for a 0.2 us difference). – Mike Boers Jun 04 '10 at 01:34
10

The _ is the same thing as x. However it's a python idiom that's used to indicate an identifier that you don't intend to use. In python these identifiers don't takes memor or allocate space like variables do in other languages. It's easy to forget that. They're just names that point to objects, in this case an integer on each iteration.

GreenMatt
  • 18,244
  • 7
  • 53
  • 79
Khorkrak
  • 3,911
  • 2
  • 27
  • 37
10

I found the various answers really elegant (especially Alex Martelli's) but I wanted to quantify performance first hand, so I cooked up the following script:

from itertools import repeat
N = 10000000

def payload(a):
    pass

def standard(N):
    for x in range(N):
        payload(None)

def underscore(N):
    for _ in range(N):
        payload(None)

def loopiter(N):
    for _ in repeat(None, N):
        payload(None)

def loopiter2(N):
    for _ in map(payload, repeat(None, N)):
        pass

if __name__ == '__main__':
    import timeit
    print("standard: ",timeit.timeit("standard({})".format(N),
        setup="from __main__ import standard", number=1))
    print("underscore: ",timeit.timeit("underscore({})".format(N),
        setup="from __main__ import underscore", number=1))
    print("loopiter: ",timeit.timeit("loopiter({})".format(N),
        setup="from __main__ import loopiter", number=1))
    print("loopiter2: ",timeit.timeit("loopiter2({})".format(N),
        setup="from __main__ import loopiter2", number=1))

I also came up with an alternative solution that builds on Martelli's one and uses map() to call the payload function. OK, I cheated a bit in that I took the freedom of making the payload accept a parameter that gets discarded: I don't know if there is a way around this. Nevertheless, here are the results:

standard:  0.8398549720004667
underscore:  0.8413165839992871
loopiter:  0.7110594899968419
loopiter2:  0.5891903560004721

so using map yields an improvement of approximately 30% over the standard for loop and an extra 19% over Martelli's.

japs
  • 1,286
  • 13
  • 27
4

Assume that you've defined do_something as a function, and you'd like to perform it N times. Maybe you can try the following:

todos = [do_something] * N  
for doit in todos:  
    doit()
Cox Chen
  • 97
  • 2
  • 50
    Sure. Let's not just call the function a million times, let's allocate a list of a million items too. If the CPU is working, shouldn't also the memory get stressed a little? The answer cannot be characterized as definitely “not useful” (it's showing a different, functioning approach) so I can't downvote, but I disagree and I'm totally opposed to it. – tzot Jun 05 '10 at 23:59
  • 1
    Isn't it just a list of N references to the same function value? – Nick McCurdy Mar 21 '16 at 01:49
  • rather better to do `fn() for fn in itertools.repeat(do_something, N)` and save pre-generating the array... this is my preferred idiom. – F1Rumors May 10 '16 at 03:49
  • 3
    @tzot Why the condescending tone? This person put effort into writing an answer and now may be discouraged from contributing in the future. Even if it has performance implications, it is a working option and especially if N is small the performance/memory implications aren't significant. – davidscolgan Oct 02 '18 at 14:41
  • I'm always surprised at how performance obsessed Python developers are :) Although I agree that it is not idiomatic, and someone new to Python reading it may not understand what is going on as clearly as when simply using an iterator – Asfand Qazi Jan 04 '19 at 12:09
  • While it is strange, it is faster then `itertools.repeat` and slower then `map` approach even for large `N`. – j123b567 Jun 01 '22 at 10:58
3

What about a simple while loop?

while times > 0:
    do_something()
    times -= 1

You already have the variable; why not use it?