Is it possible to implement a Python for range loop without an iterator variable?

135,285

Solution 1

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 | "_")*

Solution 2

You may be looking for

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

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

Solution 3

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

for _ in range(times):
    do_stuff()

Solution 4

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.')

Solution 5

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?

Share:
135,285

Related videos on Youtube

James McMahon
Author by

James McMahon

Blogging at https://dev.to/jamesmcmahon.

Updated on February 23, 2022

Comments

  • James McMahon
    James McMahon about 2 years

    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.

    • Markus Joschko
      Markus Joschko over 14 years
      This is a good question! PyDev even flags the 'i' as a warning for 'unused variable'. The solution below removes this warning.
    • Raffi Khatchadourian
      Raffi Khatchadourian over 12 years
      @Ashwin You can use \@UnusedVariable to remove that warning. Note that I needed to escape the 'at' symbol to have this comment go through.
    • tangoal
      tangoal over 4 years
      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.
  • James McMahon
    James McMahon about 15 years
    "But I think you can just live with the extra "i"" Yeah it is just an academic point.
  • Unknown
    Unknown about 15 years
    @nemo, you can try doing for _ in range(n): if you don't want to use alphanumeric names.
  • James McMahon
    James McMahon about 15 years
    Is _ a variable in that case? Or is that something else in Python?
  • Unknown
    Unknown about 15 years
    @nemo Yes its just an acceptable variable name. In the interpreter, it is automatically assigned the last expression you made.
  • James McMahon
    James McMahon about 15 years
    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
    James McMahon about 15 years
    @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.
  • Unknown
    Unknown about 15 years
    @nemo I'll make this post a wiki then.
  • James McMahon
    James McMahon about 15 years
    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
    James McMahon about 15 years
    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.
  • si28719e
    si28719e about 15 years
    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?
  • James McMahon
    James McMahon about 15 years
    Yeah why not just optimize range?
  • Alex Martelli
    Alex Martelli about 15 years
    @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.
  • kurczak
    kurczak over 14 years
    but it does exactly the same, and _ is still a variable, what's the point?
  • Cristian Ciupitu
    Cristian Ciupitu over 14 years
    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)?
  • Alex Martelli
    Alex Martelli over 14 years
    @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.
  • Cristian Ciupitu
    Cristian Ciupitu over 14 years
    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.
  • Lambda Fairy
    Lambda Fairy over 12 years
    @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.
  • ThiefMaster
    ThiefMaster about 12 years
    I think having a method such as __nonzero__ with side-effects is a horrible idea.
  • Jasmijn
    Jasmijn almost 12 years
    I would use __call__ instead. while x(): isn't that much harder to write.
  • deadly
    deadly over 11 years
    That's an infinite loop as the condition range(some_number) is always true!
  • mike rodent
    mike rodent almost 9 years
    Nice! PyDev agrees with you: this gets rid of the "Unused variable" yellow warning.
  • AChampion
    AChampion over 8 years
    chain is unnecessary, times = repeat(True, 2); while next(times, False): does the same thing.
  • Harry Moreno
    Harry Moreno about 7 years
    python grammar link 404s.
  • ShadowRanger
    ShadowRanger about 6 years
    @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 ints (implementation detail), range is similar, but also has to compute the current value, then find the cached small Python int value.
  • ShadowRanger
    ShadowRanger about 6 years
    So small Py3 ranges (Py2 xranges) are not much slower than repeat. But big ranges eventually return values outside the small int cache, so they need to allocate brand new Python ints, 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
    ShadowRanger about 6 years
    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, and making a class of the same name risks maintainer confusion (not that this isn't risking that already).
  • ShadowRanger
    ShadowRanger about 6 years
    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
    ShadowRanger about 6 years
    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
    ShadowRanger about 6 years
    @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).
  • Cristian Ciupitu
    Cristian Ciupitu about 6 years
    @ShadowRanger, for what it's worth, the documentation for 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".
  • ShadowRanger
    ShadowRanger about 6 years
    @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 ranges, 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
    ShadowRanger almost 6 years
    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() (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).
  • KeithWM
    KeithWM over 5 years
    To me this use of _ seems like a terrible idea, I wouldn't mind conflicting with it.
  • Cameron Bielstein
    Cameron Bielstein about 5 years
    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.