2

I am newbie in Python. And I am wondering how to exit out of an unbounded loop after n failures. Using counter seems for me unpythonic. This is my code:

while(True):
#get trigger state  
    trigState = scope.ask("TRIG:STATE?")
    #check if Acq complete 
    if( trigState.endswith('SAVE') ):
      print 'Acquisition complete. Writing into file ...\n'
      #save screen
      #rewrite in file  

    #check if trigger is still waiting 
    elif( trigState.endswith('READY') ):
      print 'Please wait ...\n'   
    #if trigger neither in SAVE nor in READY mode  
    else:
      #THIS IS FAILURE!!!
      print 'Failure: k tries remaining'

    #wait for several seconds before next iteration 
    time.sleep(2) 

Note that loop must be unbounded - it may iterate arbitrarily many times until number of tries exceeded.

Is there an elegant (or at least pythonic) method to meet the requirements?

toonarmycaptain
  • 2,093
  • 2
  • 18
  • 29
LRDPRDX
  • 631
  • 1
  • 11
  • 23
  • 4
    What's wrong with using a counter? It sounds like it's exactly what you need – Peter Gibson May 24 '17 at 03:57
  • Also, you mentioned the word "infinite" three times, once in the title, and twice in the question text. Can you clarify what you mean by infinite? If you have a way to exit the loop, how can it be infinite? Its true the loop runs **while** you have less than n failures, and maybe we will never get to n failures, but that doesn't mean the loop is necessarily infinite, just unbounded. Is there something else you mean by this word? – Ray Toal May 24 '17 at 04:03
  • @RayToal, You are right. The word should be unbounded. Edited. – LRDPRDX May 24 '17 at 04:05
  • Using a counter is not unpythonic - in fact it makes it very clear to other programmers what your intention is. I would say just use a counter – Peter Gibson May 24 '17 at 04:06
  • How do you keep count of the number of failures without a counter?! – donkopotamus May 24 '17 at 04:22
  • Well, one somewhat flippant answer to _that_ is to start with the list `[1] * n` and every time you have a failure, you pop the list, and your while loop's condition is that list variable itself. – Ray Toal May 24 '17 at 04:55
  • @RayToal that's still a counter and is aesthetically no different (you need to initialise it and decrement it) – donkopotamus May 24 '17 at 04:58
  • @donkopotamus I assumed the OP didn't want to use the module `counter` - since to do what is desired, I can't see a way to exit after n failures, if you don't count those failures somehow. – toonarmycaptain May 24 '17 at 14:17
  • I guess we haven't satisfied @Wolfgang, though, as they haven't accepted an answer... – toonarmycaptain May 24 '17 at 14:21
  • 1
    No, Your answers are good and I use counter. But I do not know which answer I should accept. – LRDPRDX May 25 '17 at 03:12

3 Answers3

3

To be "pythonic" you'll want to write code that conforms to the Zen of Python and a relevant rules here are:

Explicit is better than implicit.

Readability counts.

You should be explicit about the number of failures you allow, or the number of failures remaining. In the former case:

for number_of_failures in range(MAX_FAILURE):
    ...

expresses that intent, but is clunky because each time through the loop you are not necessarily failing, so you'd have to be be contorted in counting successes, which would impact readability. You can go with:

number_of_failures = 0
while number_of_failures < MAX_FAILURES:
    ...
    if this_is_a_failure_case:
        number_of_failures += 1
    ...

This is perfectly fine, as it says "if I haven't failed the maximum number of times, keep going. This is a little better than:

number_of_failures = 0
while True:
    ...
    if this_is_a_failure_case:
        number_of_failures += 1
    ...
    if number_of_failures == MAX_FAILURES: #you could use >= but this isn't necessary
        break

which hides the exit case. There are times when it is perfectly fine to exit the loop in the middle, but in your case, aborting after N failures is so crucial to what you want to do, that the condition should be in the while-loop condition.

You can also rephrase in terms of number of failures remaining:

failures_remaining = MAX_FAILURES
while failures_remaining > 0:
    ...
    if this_is_a_failure_case:
        failures_remaining -= 1
    ...

If you like functional programming, you can get rid of the loop and use tail recursion:

def do_the_thing():
    def try_it(failures_remaining = MAX_FAILURES):
        ...
        failed = ....
        ...
        try_it(failures_remaining-1 if failed else failures_remaining)

    try_it()

but IMHO that isn't really an improvement. In Python, we like to be direct and meaningful. Counters may sound clunky, but if you phrase things well (after all you are counting failures, right?) you are safely in the realm of Pythonic code.

Community
  • 1
  • 1
Ray Toal
  • 86,166
  • 18
  • 182
  • 232
  • Note that recursion is not recommended in Python, as it has a relatively small stack limit and doesn't optimize recursive functions to iterative ones. Refer to this question: https://stackoverflow.com/questions/3323001/what-is-the-maximum-recursion-depth-in-python-and-how-to-increase-it – Nearoo Oct 31 '17 at 18:47
2

Using an if statement to number your attempts seems simple enough for your purposes. It's simple, and (to my eyes anyway) easily readable, which suits your Pythonic criterion.

Within your while loop you you can do:

failure_no = 0 
max_fails = n #you can substitute an integer for this variable
while:
  #...other code
  else:
    failure_no += 1
    if failure_no == max_fails:
      break
  print 'Failure: '+str(max_fails - failure_no)+' tries remaining'

This will only iterate the number of attempts if a else/failure condition is met, if you wanted to count the total number of run throughs, not just failures, you could put the failure_no +=1 right after your while rather than within the else block.

NB You could easily put a notification inside your escape:

eg

    if failure_no == max_fails:
      print 'Maximum number of failures reached'
      break
toonarmycaptain
  • 2,093
  • 2
  • 18
  • 29
1

E.g. you can use an if statement and raise an error after n tries. You only need a limit and a counter.

while(True):
#get trigger state  
limit = 5
counter = 0
trigState = scope.ask("TRIG:STATE?")
#check if Acq complete 
if( trigState.endswith('SAVE') ):
  print 'Acquisition complete. Writing into file ...\n'
  #save screen
  #rewrite in file  

#check if trigger is still waiting 
elif( trigState.endswith('READY') ):
  print 'Please wait ...\n'   
#if trigger neither in SAVE nor in READY mode  
else:
  #THIS IS FAILURE!!!
  counter +=1
  print 'Failure: ', limit - counter ,' tries remaining'
  if k => limit: raise RuntimeError('too many attempts')


#wait for several seconds before next iteration 
time.sleep(2) 

...

TheoB
  • 141
  • 7