234

Is it possible to do following without the i?

for i in range(some_number):
    # do something

If you just want to do something N amount of times and don't need the iterator.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
James McMahon
  • 48,506
  • 64
  • 207
  • 283
  • 24
    This is a good question! PyDev even flags the 'i' as a warning for 'unused variable'. The solution below removes this warning. – Ashwin Nanjappa Nov 16 '09 at 10:26
  • 1
    @Ashwin You can use \@UnusedVariable to remove that warning. Note that I needed to escape the 'at' symbol to have this comment go through. – Raffi Khatchadourian Oct 24 '11 at 04:17
  • I head the same question you. It is annoying with pylint warnings. Of course you can disable the warnings by additional suppression like @Raffi Khatchadourian proposed. It would be nice to avoid pylint warnings **and** suppression comments. – tangoal Feb 14 '20 at 15:30

15 Answers15

138

Off the top of my head, no.

I think the best you could do is something like this:

def loop(f,n):
    for i in xrange(n): f()

loop(lambda: <insert expression here>, 5)

But I think you can just live with the extra i variable.

Here is the option to use the _ variable, which in reality, is just another variable.

for _ in range(n):
    do_something()

Note that _ is assigned the last result that returned in an interactive python session:

>>> 1+2
3
>>> _
3

For this reason, I would not use it in this manner. I am unaware of any idiom as mentioned by Ryan. It can mess up your interpreter.

>>> for _ in xrange(10): pass
...
>>> _
9
>>> 1+2
3
>>> _
9

And according to Python grammar, it is an acceptable variable name:

identifier ::= (letter|"_") (letter | digit | "_")*
ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
Unknown
  • 45,913
  • 27
  • 138
  • 182
  • 5
    "But I think you can just live with the extra "i"" Yeah it is just an academic point. – James McMahon May 04 '09 at 05:16
  • 1
    @nemo, you can try doing for _ in range(n): if you don't want to use alphanumeric names. – Unknown May 04 '09 at 05:19
  • Is _ a variable in that case? Or is that something else in Python? – James McMahon May 04 '09 at 05:20
  • 1
    @nemo Yes its just an acceptable variable name. In the interpreter, it is automatically assigned the last expression you made. – Unknown May 04 '09 at 05:24
  • Unknown if you edit all the information from your comments into your answer I'll mark it as the answer as there is the most information here. – James McMahon May 04 '09 at 05:35
  • @Unknown, thank you for the answer. I just like to have all the information from the comments aggregated in the answer, not so much for myself, but for people you may look at the question in the future. I think of stack overflow as wiki more then anything else. – James McMahon May 04 '09 at 05:48
  • @nemo I'll make this post a wiki then. – Unknown May 04 '09 at 05:48
  • Oh by wiki I just meant keeping things organized as a way for people to find canonical information quickly using say Google. Sorry I just watched Joel's tech talk on SO and I am rambling. – James McMahon May 04 '09 at 05:52
  • but it does exactly the same, and `_` is still a variable, what's the point? – kurczak Aug 31 '09 at 08:18
  • 4
    @kurczak There is a point. Using `_` makes it clear that it should be ignored. Saying there's no point in doing this is like saying there's no point in commenting your code - because it would do exactly the same anyway. – Lambda Fairy Jan 02 '12 at 03:37
  • python grammar link 404s. – Harry Moreno May 02 '17 at 18:22
  • 2
    Another good reason for `_` instead of `i` is that it appears that pylint (and perhaps other linters) will read `_` as a discard and not give an unused variable warning. – Cameron Bielstein Apr 23 '19 at 06:34
81

You may be looking for

for _ in itertools.repeat(None, times): ...

this is THE fastest way to iterate times times in Python.

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • 4
    I wasn't concerned with performance, I just was curious if there was a terser way to write the statement. While I have been using Python sporadically for about 2 years now I still feel there is a lot I am missing. Itertools is one of those things, thank you for the information. – James McMahon May 04 '09 at 05:56
  • 7
    That's interesting, I wasn't aware of that. I just took a look at the itertools docs; but I wonder why is this faster than just using range or xrange? – si28719e May 04 '09 at 06:02
  • Yeah why not just optimize range? – James McMahon May 04 '09 at 06:04
  • 6
    @blackkettle: it's faster because it doesn't need to return the current iteration index, which is a measurable part of the cost of xrange (and Python 3's range, which gives an iterator, not a list). @nemo, range is as optimized as it can be, but needing to build and return a list is inevitably heavier work than an iterator (in Py3, range does return an iterator, like Py2's xrange; backwards compatibility doesn't permit such a change in Py2), especially one that doesn't need to return a varying value. – Alex Martelli May 07 '09 at 14:17
  • 1
    itertools.repeat uses a counter just like xrange, so I still don't understand how it can be faster than xrange. Does it really matter what value the iterator yields: if it's None or an int (the counter)? – Cristian Ciupitu Sep 26 '09 at 19:12
  • 5
    @Cristian, yes, clearly preparing and returning a Python int every time, inc. gc work, does have a measurable cost -- using a counter _internally_ is no matter. – Alex Martelli Sep 26 '09 at 23:04
  • 5
    I understand now. The difference comes from the GC overhead, not from the "algorithm". By the way, I run a quick *timeit* benchmark and the speedup was ~1.42x. – Cristian Ciupitu Sep 27 '09 at 00:28
  • 1
    @CristianCiupitu: To be clear, it's not "GC overhead"; CPython, the reference implementation, is reference counted, and `int` isn't even tracked by the cycle detector (the closest thing to traditional "GC"). It's allocator/cache lookup overhead; on each loop, `repeat` only increments a C level loop counter and tests if the repeat count has been reached, increments the reference count on the single object it is repeating, then passes it back. For small `int`s (implementation detail), `range` is similar, but also has to compute the current value, then find the cached small Python `int` value. – ShadowRanger May 03 '18 at 13:08
  • 1
    So small Py3 `range`s (Py2 `xrange`s) are not much slower than `repeat`. But big `range`s eventually return values outside the small `int` cache, so they need to allocate brand new Python `int`s, populate them with the current value, then clean them up on the next loop (since a brand new object, especially an otherwise unused one usually doesn't stick around). By contrast, incrementing and decrementing the reference counts on the same object over and over never allocates or frees a thing, and, even with Python interpreter overhead, might use the object enough to keep it in faster CPU caches. – ShadowRanger May 03 '18 at 13:11
  • 1
    @ShadowRanger, for what it's worth, the documentation for [`PyLong_FromLong`](https://docs.python.org/3/c-api/long.html#c.PyLong_FromLong) says: "The current [CPython 3] implementation keeps an array of integer objects for all integers between -5 and 256, when you create an int in that range you actually just get back a reference to the existing object". – Cristian Ciupitu May 10 '18 at 12:37
  • 1
    @CristianCiupitu: I'm aware, but it's not really that important what the cache bounds are. That's why I mentioned it's an implementation detail without giving the exact range; the exact range isn't really relevant, only applies to CPython, and even then could change at any time. Either way, the point is that even when `range` is pulling cached values, it's doing more work than `repeat`, and for larger `range`s, once it exceeds the cached limit, it has to do the work of checking if it can use the cache then make the `int` anyway (and destroy it later). – ShadowRanger May 14 '18 at 04:52
75

The general idiom for assigning to a value that isn't used is to name it _.

for _ in range(times):
    do_stuff()
Ashwin Nanjappa
  • 76,204
  • 83
  • 211
  • 292
Ryan
  • 15,016
  • 6
  • 48
  • 50
20

What everyone suggesting you to use _ isn't saying is that _ is frequently used as a shortcut to one of the gettext functions, so if you want your software to be available in more than one language then you're best off avoiding using it for other purposes.

import gettext
gettext.bindtextdomain('myapplication', '/path/to/my/language/directory')
gettext.textdomain('myapplication')
_ = gettext.gettext
# ...
print _('This is a translatable string.')
Dustin Getz
  • 21,282
  • 15
  • 82
  • 131
Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
10

Here's a random idea that utilizes (abuses?) the data model (Py3 link).

class Counter(object):
    def __init__(self, val):
        self.val = val

    def __nonzero__(self):
        self.val -= 1
        return self.val >= 0
    __bool__ = __nonzero__  # Alias to Py3 name to make code work unchanged on Py2 and Py3

x = Counter(5)
while x:
    # Do something
    pass

I wonder if there is something like this in the standard libraries?

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
saffsd
  • 23,742
  • 18
  • 63
  • 67
  • 11
    I think having a method such as `__nonzero__` with side-effects is a horrible idea. – ThiefMaster Apr 22 '12 at 13:11
  • 3
    I would use `__call__` instead. `while x():` isn't that much harder to write. – Jasmijn Jul 09 '12 at 14:16
  • 2
    There is also an argument for avoiding the name `Counter`; sure, it's not reserved or in the built-in scope, but [`collections.Counter` is a thing](https://docs.python.org/3/library/collections.html#collections.Counter), and making a class of the same name risks maintainer confusion (not that this isn't risking that already). – ShadowRanger May 03 '18 at 13:17
7

You can use _11 (or any number or another invalid identifier) to prevent name-colision with gettext. Any time you use underscore + invalid identifier you get a dummy name that can be used in for loop.

JirkaV
  • 943
  • 1
  • 10
  • 16
2

May be answer would depend on what problem you have with using iterator? may be use

i = 100
while i:
    print i
    i-=1

or

def loop(N, doSomething):
    if not N:
        return
    print doSomething(N)
    loop(N-1, doSomething)

loop(100, lambda a:a)

but frankly i see no point in using such approaches

Anurag Uniyal
  • 85,954
  • 40
  • 175
  • 219
  • 1
    Note: Python (definitely not the CPython reference interpreter at least, probably not most of the others) does not optimize out tail recursion, so N will be limited to something in the neighborhood of the value of [`sys.getrecursionlimit()`](https://docs.python.org/3/library/sys.html#sys.getrecursionlimit) (which defaults to somewhere in the low four digit range on CPython); using `sys.setrecursionlimit` would raise the limit, but eventually you'd hit the C stack limit and the interpreter would die with a stack overflow (not just raising a nice `RuntimeError`/`RecursionError`). – ShadowRanger Jun 20 '18 at 03:10
0

Instead of an unneeded counter, now you have an unneeded list. Best solution is to use a variable that starts with "_", that tells syntax checkers that you are aware you are not using the variable.

x = range(5)
while x:
  x.pop()
  print "Work!"
ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
Robert Jacobs
  • 3,266
  • 1
  • 20
  • 30
0

We have had some fun with the following, interesting to share so:

class RepeatFunction:
    def __init__(self,n=1): self.n = n
    def __call__(self,Func):
        for i in xrange(self.n):
            Func()
        return Func


#----usage
k = 0

@RepeatFunction(7)                       #decorator for repeating function
def Job():
    global k
    print k
    k += 1

print '---------'
Job()

Results:

0
1
2
3
4
5
6
---------
7
Developer
  • 8,258
  • 8
  • 49
  • 58
0

If do_something is a simple function or can be wrapped in one, a simple map() can do_something range(some_number) times:

# Py2 version - map is eager, so it can be used alone
map(do_something, xrange(some_number))

# Py3 version - map is lazy, so it must be consumed to do the work at all;
# wrapping in list() would be equivalent to Py2, but if you don't use the return
# value, it's wastefully creating a temporary, possibly huge, list of junk.
# collections.deque with maxlen 0 can efficiently run a generator to exhaustion without
# storing any of the results; the itertools consume recipe uses it for that purpose.
from collections import deque

deque(map(do_something, range(some_number)), 0)

If you want to pass arguments to do_something, you may also find the itertools repeatfunc recipe reads well:

To pass the same arguments:

from collections import deque
from itertools import repeat, starmap

args = (..., my args here, ...)

# Same as Py3 map above, you must consume starmap (it's a lazy generator, even on Py2)
deque(starmap(do_something, repeat(args, some_number)), 0)

To pass different arguments:

argses = [(1, 2), (3, 4), ...]

deque(starmap(do_something, argses), 0)
ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
mtd
  • 2,224
  • 19
  • 21
0

We can use the while & yield, we can create our own loop function like this. Here you can refer to the official documentation.

def my_loop(start,n,step = 1):
    while start < n:
        yield start
        start += step

for x in my_loop(0,15):
    print(x)
Kushal Bhavsar
  • 388
  • 3
  • 6
0

In an answer to a related question, @japs timed four approaches at doing do_something() N times: a throwaway variable (standard), an underscore, itertools.repeat() (loopiter), and map(do_something, itertools.repeat()) (loopiter2). This yielded this comparison with clear differences:

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

I decided to loop inside do_something() so that it actually takes some time. This yielded this comparison:

standard: 8.756060799933039
underscore: 8.730245499988087
loopiter: 8.643029399914667
loopiter2: 8.489477000082843

My conclusion is that the cost of looping pales in comparison with the cost of doing something. The moral of this story is its better to focus on optimizing what you do instead of how you loop.

mobatemacs
  • 41
  • 3
0

I generally agree with solutions given above. Namely with:

  1. Using underscore in for-loop (2 and more lines)
  2. Defining a normal while counter (3 and more lines)
  3. Declaring a custom class with __nonzero__ implementation (many more lines)

If one is to define an object as in #3 I would recommend implementing protocol for with keyword or apply contextlib.

Further I propose yet another solution. It is a 3 liner and is not of supreme elegance, but it uses itertools package and thus might be of an interest.

from itertools import (chain, repeat)

times = chain(repeat(True, 2), repeat(False))
while next(times):
    print 'do stuff!'

In these example 2 is the number of times to iterate the loop. chain is wrapping two repeat iterators, the first being limited but the second is infinite. Remember that these are true iterator objects, hence they do not require infinite memory. Obviously this is much slower then solution #1. Unless written as a part of a function it might require a clean up for times variable.

Yauhen Yakimovich
  • 13,635
  • 8
  • 60
  • 67
-2

If you really want to avoid putting something with a name (either an iteration variable as in the OP, or unwanted list or unwanted generator returning true the wanted amount of time) you could do it if you really wanted:

for type('', (), {}).x in range(somenumber):
    dosomething()

The trick that's used is to create an anonymous class type('', (), {}) which results in a class with empty name, but NB that it is not inserted in the local or global namespace (even if a nonempty name was supplied). Then you use a member of that class as iteration variable which is unreachable since the class it's a member of is unreachable.

skyking
  • 13,817
  • 1
  • 35
  • 57
  • 1
    Obviously this is intentionally pathological, so criticizing it is beside the point, but I will note an additional pitfall here. On CPython, the reference interpreter, class definitions are naturally cyclic (creating a class unavoidably creates a reference cycle that prevents deterministic cleanup of the class based on reference counting). That means you're waiting on cyclic GC to kick in and clean up the class. It will usually be collected as part of the younger generation, which by default is collected frequently, but even so, each loop means ~1.5 KB of garbage w/non-deterministic lifetime. – ShadowRanger May 03 '18 at 13:46
  • Basically, to avoid a named variable that would be (typically) deterministically cleaned up on each loop (when it's rebound, and the old value cleaned up), you're making a huge unnamed variable that is cleaned non-deterministically, and could easily last longer. – ShadowRanger May 03 '18 at 13:47
-8

What about:

while range(some_number):
    #do something
  • 4
    That's an infinite loop as the condition `range(some_number)` is always true! – deadly Oct 11 '12 at 10:44
  • 1
    @deadly: Well, if `some_number` is less than or equal to `0`, it's not infinite, it just never runs. :-) And it's rather inefficient for an infinite loop (especially on Py2), since it creates a fresh `list` (Py2) or `range` object (Py3) for each test (it's not a constant from the interpreter's point of view, it has to load `range` and `some_number` every loop, call `range`, then test the result). – ShadowRanger May 03 '18 at 14:07