Break out of while loop handling keybreak exception correctly - python

I need to implement a function which monitors the status of a running job until it is completed. Something like this:
def monitor_job(job_id):
ended = False
while not ended:
ended = check_if_ended(job_id)
time.sleep(5)
print('ended')
Now I want to allow a user to break the loop using Ctrl-C, and in that case I want to stop the job gracefully calling a stop_job() function. However this function is asyncronous, it just submits a request to stop the job, which can take some time to complete. Therefore after this call I would like to continue the while loop and wait for the job to end. Here is an attempt:
def monitor_job(job_id):
keybreaks = False
ended = False
while not ended:
# (...)
try:
ended = check_if_ended(job_id)
time.sleep(5)
except KeyboardInterrupt:
# break after a second keybreak
if keybreaks:
raise
keybreaks = True
stop_job(job_id)
print('ended')
What I don't like in this solution is that if, by chance, Ctrl-C is triggered outside of the try (for example at the (...)) then the exception would not be catched, and the job would not be stopped gracefully.
Is there a good way to handle this correctly, avoiding such kind of timing problem?
I have thought of some solution like: replicating the while loop inside the except block, which isn't elegant; or putting the try outside of the while loop and then calling back monitor_job() in the except, but then the Ctrl-C might happen during the function call.

It is indeed possible that the Ctrl-C happens at the (...). But it is only a very! small chance.
If you want to be absolutely certain you could use the following snippet:
def monitor_job(job_id, first_call=True):
ended = False
try:
if first_call:
monitor_job(job_id, first_call=False)
while not ended:
ended = check_if_ended(job_id)
time.sleep(5)
except KeyboardInterrupt:
if first_call:
raise
It also uses a recursion like you proposed but not in the except block but in the try block. So when you hit Ctrl-C you first exit the inner call to monitor_job(first_call=False) and only after the second Ctrl-C do you exit the outer function.
That is the most elegant solution I was able to come up with.

Related

API Exception Handling (Python)

I request data from an API in a while/try/except loop.
The general structure is like this:
while (True):
try:
apiloop
except:
print(error)
else:
apiloop
If I try it like this it continues the loop after the first exceptions, as expected.
But in the next round of exceptions, it crashes again.
So my question is how do I get to catch the exception every time and still continue with my loop. Do I need to set maybe:
finally:
continue
I appreciate every help.
Regards,
Angelo (Germany)

Python 2.7 - weird behaviour on finally clause

I've run into a very strange problem, that is making me wonder if I understand exception handling at all.
I have a code (that I'll post at the end) that looks more or less like this:
try:
doSomething()
finally:
print 'bye'
The code in the finally clause is not being executed when I exit my program via ctrl+c.
To make matters worse, now consider the following:
try:
doSomething()
except: # this could be replaced by except Exception, it doesn't matter
print 'something'
finally:
print 'bye'
Now the code in the except clause is not executed.. but the code in the finally clause is!
I realize this has to be the fault of the code executed by doSomething(). But my question is, how is it even possible? I thought we could be 100% confident that finally clauses always got executed.
Here goes the real code. It's running on a raspberry pi 3. It's an adaptation of the code found here.
import RPi.GPIO as GPIO, time
GPIO.setmode(GPIO.BCM)
# Define function to measure charge time
def RCtime (PiPin):
# Discharge capacitor
GPIO.setup(PiPin, GPIO.OUT)
GPIO.output(PiPin, GPIO.LOW)
time.sleep(.1)
time1 = time.time()
GPIO.setup(PiPin, GPIO.IN)
if (GPIO.input(PiPin) == GPIO.LOW):
GPIO.wait_for_edge(PiPin, GPIO.RISING, timeout=1000)
time_elap = time.time()-time1
return time_elap*1e3
# Main program loop
try:
while True:
print RCtime(4) # Measure timing using GPIO4
except Exception:
print '---------got ya-----------------'
finally:
print '---Finaly---'
GPIO.cleanup() # this ensures a clean exit
To be more specific, the behaviour depicted appears when the program is waiting at the GPIO.wait_for_edge(PiPin, GPIO.RISING, timeout=1000) line.
If your code does not handle the KeyboardInterrupt exception, the system has to do it: which means it kills the code. Instead, if you have an exception handler, your code takes over, leaves the loop and then executes the finally block.
Well, this seems to be a bug related to the way RPi.GPIO handles signals (such as KeyboardInterrupt). This module invokes some functions written in c. The function is sort of catching the KeyboardInterrupt and throwing an exception, but it doesn't do it properly. Instead of getting only one exception, the two exceptions get "stacked". Because of that, the first thing that run after an exception in my code is going to be terminated by the second exception. If I don't include an except block, the finally block gets executed after the first exception, and is terminated by the second exception. If I include an except block, it tries to run after the first exception, it fails because of the second exception, and then it goes to the finally block.
I only found one forum where people deal with a similar problem.
After my previous experiment I was curious how this works with
input() [https://svn.python.org/projects/python/trunk/Parser/myreadline.c]. I replaced the sem.acquire() with raw_input() and ran the same
tests. Now the inner exception is really taken so it works like the OP
expected. The exception, however is KeyboardInterrupt, not the special
exception from the IPC module.
So I looked in the source code how they did it:
The code is in Parser/myreadline.c.
This code for input in function calls PyErr_CheckSignals() and
PyOS_InterruptOccurred() for a proper handling of the interrupt. So it
seems the OP should do something similar. Onl;y to deliver the custom
error you will have to do some other stuff. I don't know what but maybe
calling PyErr_SetString is sufficient as it might overwrite the
KeyboardInterrupt stuff.

How to stop code execution of for loop from try/except in a Pythonic way?

I've got some Python code as follows:
for emailCredentials in emailCredentialsList:
try:
if not emailCredentials.valid:
emailCredentials.refresh()
except EmailCredentialRefreshError as e:
emailCredentials.active = False
emailCredentials.save()
# HERE I WANT TO STOP THIS ITERATION OF THE FOR LOOP
# SO THAT THE CODE BELOW THIS DOESN'T RUN ANYMORE. BUT HOW?
# a lot more code here that scrapes the email box for interesting information
And as I already commented in the code, if the EmailCredentialRefreshError is thrown I want this iteration of the for loop to stop and move to the next item in the emailCredentialsList. I can't use a break because that would stop the whole loop and it wouldn't cover the other items in the loop. I can of course wrap all the code in the try/except, but I would like to keep them close together so that the code remains readable.
What is the most Pythonic way of solving this?
Try using the continue statement. This continues to the next iteration of the loop.
for emailCredentials in emailCredentialsList:
try:
if not emailCredentials.valid:
emailCredentials.refresh()
except EmailCredentialRefreshError as e:
emailCredentials.active = False
emailCredentials.save()
continue
<more code>

Python - How to stop code from continuing

I have a function that enables and disables services from the Netscaler. I pass in either enable or disable.
However, I want it to exit in the event someone passes a different value. I tried breaking out of it but it tells me my break is outside the loop. How can I resolve this?
def servicegroup_action(servicegroup_name,action):
if not action.upper() in ('ENABLE','DISABLE'):
break
try:
# do stuff
except NSNitroError, e:
print e.message
I think you are looking for return so as to terminate the function execution; a break is used to break out of a loop, and within your current code, there is no associated for/while loop.
The other option is to execute your code only if the correct values are passed, by indenting the try-except within the if block:
def servicegroup_action(servicegroup_name,action):
if action.upper() in ('ENABLE','DISABLE'):
try:
# do stuff
except NSNitroError, e:
print e.message

When while loop placed in wxPython events

I'm trying to write a GUI program grabbing specific contents from a webpage. The idea is when I hit the start button, the program should start extracting information from that page. And I want to add some code to check if connected to the Internet. If not, continue trying until connected.
So I just added the following code in the event, but found it didn't work. Also the whole program has to be closed in a forced way. Here's my code:
import urllib2
import time
InternetNotOn = True
while InternetNotOn:
try:
urllib2.urlopen("http://google.com")
InternetNotOn = False
print "Everyting is fine!"
except urllib2.URLError, e:
print "Error!"
time.sleep(10)
What could the problem be?
When you have an event based program, the overall flow of the program is this:
while the-program-is-running:
wait-for-an-event
service-the-event
exit
Now, lets see what happens when service-the-event calls something with a (potentially) infinite loop:
while the-program-is-running:
wait-for-an-event
while the-internet-is-on:
do-something
exit
Do you see the problem? In the worse case your program may never call wait-for-an-event again because your loop is running.
Remember: the event loop is already an infinite loop, you don't need to add another infinite loop inside of it. Instead, take advantage of the existing loop. You can use wx.CallAfter or wx.CallLater to call a method which will cause your function to be called at the next iteration of the event loop.
Then, within your function you call wx.CallAfter or wx.CallLater again to cause it to again be called on the next iteration of the event loop.
Instead of time.sleep(10) you can call wxApp::Yield and time.sleep(1) ten times.
Beware of reentrancy problems (e.g. pressing the start button again.). The start button could be dimmed while in the event handler.
But Bryan Oakley's solution is probably the better way.

Categories