49

Say I have a function foo that I want to call n times. In Ruby, I would write:

n.times { foo }

In Python, I could write:

for _ in xrange(n): foo()

But that seems like a hacky way of doing things.

My question: Is there an idiomatic way of doing this in Python?

8
  • 4
    Don't forget that ruby also computes the counter variable for the loop. In particular n.times{|i| foo}. In your case you are just discarding it. So why is this ok in ruby but hacky in python? Commented Apr 17, 2010 at 3:34
  • 7
    Because I don't have to throw it away explicitly. :-) The Ruby version also demonstrates the intent of the line better, IMO. Commented Apr 17, 2010 at 3:52
  • I use x as the throw away variable, but _ seems more idiomatic. And yes, python doesn't have a times function .. Commented Apr 17, 2010 at 4:05
  • 1
    @perimosocoriade if you are concerned about "demonstrating your intent" clearly to other python developers, the _ is the way to go. Commented Apr 17, 2010 at 4:28
  • 1
    @drozzy part of the reason ruby blocks dont enforce arity is so you can throwaway variables exactly as in this example -- it's not going to hurt performance, and it's idiomatic. Nothing hacky about it. Commented Jun 24, 2013 at 4:18

4 Answers 4

44

You've already shown the idiomatic way:

for _ in range(n): # or xrange if you are on 2.X
    foo()

Not sure what is "hackish" about this. If you have a more specific use case in mind, please provide more details, and there might be something better suited to what you are doing.

Sign up to request clarification or add additional context in comments.

6 Comments

I only think it's hackish because it's generating the range values that I throw away with the _ convention. It seems like I'm sidestepping, for some reason.
That's why you use _ instead of some other name. It means "throw away variable" -- the idea of _ as a throw-away is common to Python, Prolog, Haskell, and presumably some other languages.
Is range not inefficient because it uses n bytes of memory for the counter, whereas a C for loop would use only one byte for the counter?
+1 for the use of _, didn't know that one!
This should be the accepted answer.
|
18

If you want the times method, and you need to use it on your own functions, try this:

def times(self, n, *args, **kwargs):
    for _ in range(n):
        self.__call__(*args, **kwargs)

import new
def repeatable(func):
    func.times = new.instancemethod(times, func, func.__class__)
    return func

now add a @repeatable decorator to any method you need a times method on:

@repeatable
def foo(bar):
    print bar

foo.times(4, "baz") #outputs 4 lines of "baz"

Comments

16

Fastest, cleanest is itertools.repeat:

import itertools

for _ in itertools.repeat(None, n):
    foo()

3 Comments

That doesn't get rid of the unused variable.
I timed a couple methods, and the itertools one is about 50% faster than xrange. I wouldn't say it's any cleaner, though.
@dan04, what "unused variable" -- _?! Puh-Leahze!-)
10

The question pre-supposes that calling foo() n times is an a priori necessary thing. Where did n come from? Is it the length of something iterable? Then iterate over the iterable. As I am picking up Python, I find that I'm using few to no arbitrary values; there is some more salient meaning behind your n that got lost when it became an integer.

Earlier today I happened upon Nicklaus Wirth's provocative paper for IEEE Computer entitled Good Ideas - Through the Looking Glass (archived version for future readers). In section 4 he brings a different slant on programming constructs that everyone (including himself) has taken for granted but that hold expressive flaws:

"The generality of Algol’s for statement should have been a warning signal to all future designers to always keep the primary purpose of a construct in mind, and to be weary of exaggerated generality and complexity, which may easily become counter-productive."

The algol for is equivalent to the C/Java for, it just does too much. That paper is a useful read if only because it makes one not take for granted so much that we so readily do. So perhaps a better question is "Why would you need a loop that executes an arbitrary number of times?"

13 Comments

You're right, there are very few cases where this is needed. There are some, though. In my case, I needed to skip a set number of lines through a file before reading data. Also, benchmarking code tends to have a need for arbitrary numbers of runs. Things that affect some global state (like IO operations) are the most common culprits.
If you want to skip a set number of lines from a file you could use: next(itertools.islice(thefile, n-1, n)) This has the advantage of speed, though it lacks the clarity of the for loop.
Sadly, those IEEE links are now broken. The article is available for subscribers, or to purchase, from computer.org/portal/web/csdl/doi/10.1109/MC.2006.20.
@perimosocordiae: you could skip n lines in a file using consume() itertools' recipe: next(islice(file, n, n), None)
I agree with the sentiment, but I just don't see how this answers the question...
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.