60

I was wondering if it is possible to perform a certain number of operations without storing the loop iteration number anywhere.

For instance, let's say I want to print two "hello" messages to the console. Right now I know I can do:

for i in range(2):
    print "hello"

but then the i variable is going to take the values 0 and 1 (which I don't really need). Is there a way to achieve the same thing without storing those unwanted values anywhere?

Needless to say, using a variable is not a big deal at all... I'm just curious.

Savir
  • 17,568
  • 15
  • 82
  • 136

9 Answers9

55

The idiom (shared by quite a few other languages) for an unused variable is a single underscore _. Code analysers typically won't complain about _ being unused, and programmers will instantly know it's a shortcut for i_dont_care_wtf_you_put_here. There is no way to iterate without having an item variable - as the Zen of Python puts it, "special cases aren't special enough to break the rules".

  • 4
    I don't understand why such a thing like AutoHotkey's `Loop, 5 { ...` (simply repeat an action 5 times) does not exist anywhere else. No variables required an provides better readability. – phil294 Jun 16 '16 at 14:31
  • _ is also used for ```gettext``` i18n and its interpreter predefined variable. ```i``` looks much better – ubombi Jul 18 '16 at 14:10
  • @phil294 I suppose sometimes if you're refactoring, you might need the variable, so it's easier to just change its name than changing the whole loop. – wjandrea May 27 '21 at 22:28
20
exec 'print "hello";' * 2

should work, but I'm kind of ashamed that I thought of it.

Update: Just thought of another one:

for _ in " "*10: print "hello"
recursive
  • 83,943
  • 34
  • 151
  • 241
18

Well I think the forloop you've provided in the question is about as good as it gets, but I want to point out that unused variables that have to be assigned can be assigned to the variable named _, a convention for "discarding" the value assigned. Though the _ reference will hold the value you gave it, code linters and other developers will understand you aren't using that reference. So here's an example:

for _ in range(2):
    print('Hello')
ThorSummoner
  • 16,657
  • 15
  • 135
  • 147
8

Others have addressed the inability to completely avoid an iteration variable in a for loop, but there are options to reduce the work a tiny amount. range has to generate a whole bunch of numbers after all, which involves a tiny amount of work; if you want to avoid even that, you can use itertools.repeat to just get the same (ignored) value back over and over, which involves no creation/retrieval of different objects:

from itertools import repeat

for _ in repeat(None, 200):  # Runs the loop 200 times
    ...

This will run faster in microbenchmarks than for _ in range(200):, but if the loop body does meaningful work, it's a drop in the bucket. And unlike multiplying some anonymous sequence for your loop iterable, repeat has only a trivial setup cost, with no memory overhead dependent on length.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
5

Although I agree completely with delnan's answer, it's not impossible:

loop = range(NUM_ITERATIONS+1)
while loop.pop():
    do_stuff()

Note, however, that this will not work for an arbitrary list: If the first value in the list (the last one popped) does not evaluate to False, you will get another iteration and an exception on the next pass: IndexError: pop from empty list. Also, your list (loop) will be empty after the loop.

Just for curiosity's sake. ;)

Walter
  • 7,809
  • 1
  • 30
  • 30
  • Warning, this is Py2 only. On Py3, `range` is a dedicated immutable sequence type, not a `list`. That said, I do appreciate the effort at cleverness for the sake of cleverness, so to chip in, here's a working variation for Py3.2+ (one that avoids the overhead of of making a `list`, so it's more efficient). First, initialize with `loop = reversed(range(NUM_ITERATIONS+1))`, then make the loop opening `while next(loop):`. That will produce the same values in the same order by using a reverse iterator over the `range` object (generating them on demand instead of eagerly up front). – ShadowRanger Mar 16 '22 at 14:29
  • A Py2 version could be made with `xrange` too, though it's slightly uglier (`xrange` doesn't support reverse iteration, so you have to manually provide all three arguments, and operating in reverse order that way looks a little uglier to set the end point): Initialization would be `loop = iter(xrange(NUM_ITERATIONS, -1, -1))`, and the `while` remains the same as in Py3 (at least on 2.6+; before 2.6, you'd need to do `while loop.next():`, but let's hope no one is still using 2.5 or earlier two years after even 2.7 ended support). – ShadowRanger Mar 16 '22 at 14:32
3

This will print 'hello' 3 times without storing i...

[print('hello') for i in range(3)]
Paul Roub
  • 36,322
  • 27
  • 84
  • 93
  • 2
    This is the same as the OP's original code, just presented as a list comprehension instead of a for loop. – Thierry Lathuille Jun 21 '17 at 13:28
  • 1
    I think that `i` will not exist outside of the list comprehension though, which is different from the for loop – Sam Hartman Jun 21 '17 at 19:18
  • @SamHartman: The `i` will exist outside the listcomp on Py2, but not on Py3. List comprehensions have no separate scope on Py2, but in Py3 (and for genexprs and setcomp/dictcomps on Py2) they operate in a closure scope where the iteration variable is local to the closure scope, and can't be seen outside it. The discrepancy between listcomps and all other comprehensions/genexprs was one of the things that was simplified (in a compatibility breaking way) between Py2 and Py3. – ShadowRanger Apr 16 '18 at 23:12
2

Sorry, but in order to iterate over anything in any language, Python and English included, an index must be stored. Be it in a variable or not. Finding a way to obscure the fact that python is internally tracking the for loop won't change the fact that it is. I'd recommend just leaving it as is.

1
for word in ['hello'] * 2:
    print word

It's not idiomatic Python, but neither is what you're trying to do.

nmichaels
  • 49,466
  • 12
  • 107
  • 135
  • For example, you need an unused variable to generate a list of random numbers. – wRAR Sep 10 '10 at 15:50
  • The thing is, the variable isn't actually unused. Somebody has to keep track of how many times to iterate, and how many times the loop has been executed. – nmichaels Sep 10 '10 at 16:11
  • @Nathon: Wrong. Since `for i in seq: stuff()` is equivalent to (forgive me the braces, but comments don't preverve newlines) `_iterator = iter(seq); while True { try { i = next(_iterator) } except StopIteration { break } stuff() }`, we could remove the `i = ` part from the desugared version and end up with no iteration variable. All iteration state is managed by the iterator, not by the iteration variable. –  Sep 10 '10 at 16:17
  • @delnan I meant down in the guts. The implementation still needs to keep track (even if it's hiding somewhere in an iterator class). There will always be some memory somewhere devoted to keeping track of where in the iteration we are. It may be inaccessible, but that doesn't make it not exist. – nmichaels Sep 10 '10 at 17:34
  • @nmichaels: You can iterate without an iteration variable hidden even in guts of a CPU register e.g., `def g(): { while True: { if random.random() < 0.1: { break } else { yield 1 } } }`. `g()` is a generator; at any given time there is a 1 in 10 chance that the iteration stops and there is no any iteration variable (strictly speaking `random.random()` has an internal state that you can argue is a substitute for the iteration variable but we can implement `random.random()` in terms of external source e.g., http://www.random.org/ in that case nothing can predict when the iteration stops) – jfs Mar 19 '11 at 13:40
  • @J.F. Sebastian: Alright, granted. But what good is it? Assuming you want deterministic behavior, there must be some state maintained at some level to determine that behavior. I think that assumption doesn't stretch credibility. – nmichaels Mar 21 '11 at 15:20
1

You can simply do

print 2*'hello'
Varpie
  • 57
  • 1
  • 8
sairam546
  • 73
  • 3
  • 2
    That's a solution only for the _example_ OP provides. It doesn't answer all instances. – Nae Nov 15 '17 at 18:20