Can I save results anyway even when Keyboardinterrupt? - python

I have a very long code which is taking forever to run. I was wondering if there is a way to save the results even if I use the keyboard to interrupt the code from running? All the examples I found were using except with Keyboardinterrupt, so I don't know if this is the right code to use.
More concretely: I have a code which ends with saving results in a list, and returning the list. In this case, is there a way to return the list despite keyboardinterrupt? Can I use if keyboardinterrupt statement?
My code:
# removed is a very long list
for a, b in itertools.combinations(removed, 2):
temp = [a,b]
Token_Set_Ratio = fuzz.token_set_ratio(temp[0],temp[1])
if Token_Set_Ratio > k:
c = random.choice(temp)
if c in removed:
removed.remove(c)
else:
pass
else:
pass
return removed
Where can I add the part for python to retain removed even if keyboard interrupt occurs?

You could use a try-except with KeyboardInterrupt:
def your_function():
removed = [...]
try:
# Code that takes long time
for a, b in itertools.combinations(removed, 2):
...
return removed
except KeyboardInterrupt:
return removed
A small example:
import time
def foo():
result = []
try:
# Long running code
for i in range(10000):
result.append(i)
time.sleep(0.1)
return result
except KeyboardInterrupt:
# Code to "save"
return result
print(foo())
When you Ctrl-C before the end of execution, a partial list is printed.

Related

Redo for loop iteration in Python

Does Python have anything in the fashion of a "redo" statement that exists in some languages?
(The "redo" statement is a statement that (just like "break" or "continue") affects looping behaviour - it jumps at the beginning of innermost loop and starts executing it again.)
No, Python doesn't have direct support for redo. One option would something faintly terrible involving nested loops like:
for x in mylist:
while True:
...
if shouldredo:
continue # continue becomes equivalent to redo
...
if shouldcontinue:
break # break now equivalent to continue on outer "real" loop
...
break # Terminate inner loop any time we don't redo
but this mean that breaking the outer loop is impossible within the "redo-able" block without resorting to exceptions, flag variables, or packaging the whole thing up as a function.
Alternatively, you use a straight while loop that replicates what for loops do for you, explicitly creating and advancing the iterator. It has its own issues (continue is effectively redo by default, you have to explicitly advance the iterator for a "real" continue), but they're not terrible (as long as you comment uses of continue to make it clear you intend redo vs. continue, to avoid confusing maintainers). To allow redo and the other loop operations, you'd do something like:
# Create guaranteed unique sentinel (can't use None since iterator might produce None)
sentinel = object()
iterobj = iter(mylist) # Explicitly get iterator from iterable (for does this implicitly)
x = next(iterobj, sentinel) # Get next object or sentinel
while x is not sentinel: # Keep going until we exhaust iterator
...
if shouldredo:
continue
...
if shouldcontinue:
x = next(iterobj, sentinel) # Explicitly advance loop for continue case
continue
...
if shouldbreak:
break
...
# Advance loop
x = next(iterobj, sentinel)
The above could also be done with a try/except StopIteration: instead of two-arg next with a sentinel, but wrapping the whole loop with it risks other sources of StopIteration being caught, and doing it at a limited scope properly for both inner and outer next calls would be extremely ugly (much worse than the sentinel based approach).
No, it doesn't. I would suggest using a while loop and resetting your check variable to the initial value.
count = 0
reset = 0
while count < 9:
print 'The count is:', count
if not someResetCondition:
count = count + 1
This is my solution using iterators:
class redo_iter(object):
def __init__(self, iterable):
self.__iterator = iter(iterable)
self.__started = False
self.__redo = False
self.__last = None
self.__redone = 0
def __iter__(self):
return self
def redo(self):
self.__redo = True
#property
def redone(self):
return self.__redone
def __next__(self):
if not (self.__started and self.__redo):
self.__started = True
self.__redone = 0
self.__last = next(self.__iterator)
else:
self.__redone += 1
self.__redo = False
return self.__last
# Display numbers 0-9.
# Display 0,3,6,9 doubled.
# After a series of equal numbers print --
iterator = redo_iter(range(10))
for i in iterator:
print(i)
if not iterator.redone and i % 3 == 0:
iterator.redo()
continue
print('---')
Needs explicit continue
redone is an extra feature
For Python2 use def next(self) instead of def __next__(self)
requires iterator to be defined before the loop
I just meet the same question when I study perl,and I find this page.
follow the book of perl:
my #words = qw(fred barney pebbles dino wilma betty);
my $error = 0;
my #words = qw(fred barney pebbles dino wilma betty);
my $error = 0;
foreach (#words){
print "Type the word '$_':";
chomp(my $try = <STDIN>);
if ($try ne $_){
print "Sorry - That's not right.\n\n";
$error++;
redo;
}
}
and how to achieve it on Python ??
follow the code:
tape_list=['a','b','c','d','e']
def check_tape(origin_tape):
errors=0
while True:
tape=raw_input("input %s:"%origin_tape)
if tape == origin_tape:
return errors
else:
print "your tape %s,you should tape %s"%(tape,origin_tape)
errors += 1
pass
all_error=0
for char in tape_list:
all_error += check_tape(char)
print "you input wrong time is:%s"%all_error
Python has not the "redo" syntax,but we can make a 'while' loop in some function until get what we want when we iter the list.
Not very sophiscated but easy to read, using a while and an increment at the end of the loop. So any continue in between will have the effect of a redo. Sample to redo every multiple of 3:
redo = True # To ends redo condition in this sample only
i = 0
while i<10:
print(i, end='')
if redo and i % 3 == 0:
redo = False # To not loop indifinively in this sample
continue # Redo
redo = True
i += 1
Result: 00123345667899
There is no redo in python.
A very understandable solution is as follow:
for x in mylist:
redo = True
while redo:
redo = False
If should_redo:
redo = True
It's clear enough to do not add comments
Continue will work as if it was in the for loop
But break is not useable, this solution make break useable but the code is less clear.
Here is a solution for python 3.8+ since now we have the := operator:
for key in mandatory_attributes: # example with a dictionary
while not (value := input(f"{key} (mandatory): ")):
print("You must enter a value")
mandatory_attributes[key] = value

Capture StopIteration Error Message in For Loop

I have code similar to this structure:
def my_gen(some_str):
if some_str == "":
raise StopIteration("Input was empty")
else:
parsed_list = parse_my_string(some_str)
for p in parsed_list:
x, y = p.split()
yield x, y
for x, y in my_gen()
# do stuff
# I want to capture the error message from StopIteration if it was raised manually
Is it possible to do this by using a for loop? I couldn't find a case similar to this elsewhere.
If using a for loop isn't possible, what are some other alternatives?
Thanks
You cannot do this in a for loop - because a for loop will implicitly catch the StopIteration exception.
One possible way to do this is with an infinite while:
while True:
try:
obj = next(my_gen)
except StopIteration:
break
print('Done')
Or you can use any number of consumers from the itertools library - have a look at the recipe section at the bottom for examples.

Diff between "return" and "yield" stmts in generators

Have a look at the code:
def main():
for p in test1(): print(p)
def test1():
s = set()
s.update(range(5))
for p in s: yield p
return s
Why I got only 0,1,2,3,4? The output should be: 0,1,2,3,4 two times (1 for 'yield' and 1 for 'return')
PS: Python-3.4
A return statement in a Python 3.3+ generator doesn't do what you think it does. The value is not returned to the caller like in a normal function, but added as an attribute on the StopIteration exception the generator raises to signal that it is done iterating. The behavior you're seeing in your loop is unrelated.
First, lets understand the loop behavior. This comes down to a simple fact: The loop variable (e.g. i) doesn't go out of scope when a for loop ends:
for i in range(5): # this loop will print 0 through 4
print(i)
print(i) # this line will print 4 again, since 4 it was the last value assigned to i
Your code is doing exactly this. The else clause you're using does nothing special, since you never break out of the loop. (Neftas's answer explains what an else attached to a loop is for.)
As for where the return value is going, you can find it if you iterate over your generator manually:
gen = test1()
print(next(gen)) # prints 0
print(next(gen)) # prints 1
print(next(gen)) # prints 2
print(next(gen)) # prints 3
print(next(gen)) # prints 4
print(next(gen)) # prints set([0,1,2,3,4]) from the last yield statement
try:
next(gen)
except StopIteration as e:
print(e.value) # prints set([0,1,2,3,4]) from the return statement
This isn't a very common usage. The usual way of getting at the returned value is by using the result of a yield from expression in another generator:
def test3():
print(yield from test1())
This is a generator that yields all the same values as test1, but it also prints out the value that test1 returns.
I don't think the return idiom is terribly useful in most situations. yield from can be very useful in recursive or otherwise complex generators, but I've never found a need to return a value from one.
If you want more information about the yield from expression and the return value from generators, read PEP 380, which describes the new features that were added in Python 3.3.
For a discussion of return in a generator, see Blckknght's answer, this is about the else clause in a for loop.
I read a nice article about else clauses in for loops if you don't like the documentation.
The gist of it is that an else clause in a for loop is all about completion, rather than about conditionals. Compare:
if 1:
print "True"
else:
print "False"
Here, the else clause is executed when the comparison falls through.
But:
for i in xrange(5):
if i == 123:
print "Found it!"
break
else:
print "Value not in list"
# output: "Value not in list"
Here, the else clause gets executed unless the flow of execution hits the break statement:
for i in xrange(5):
if i == 4:
print "Found it!"
break
else:
print "Value not in list"
# output: "Found it!"
If your remove the break, both strings will be printed. In your code the flow of execution will always reach the else statement, so the code there is run.

break and continue in function

def funcA(i):
if i%3==0:
print "Oh! No!",
print i
break
for i in range(100):
funcA(i)
print "Pass",
print i
I know script above won't work. So, how can I write if I need put a function with break or continue into a loop?
A function cannot cause a break or continue in the code from which it is called. The break/continue has to appear literally inside the loop. Your options are:
return a value from funcA and use it to decide whether to break
raise an exception in funcA and catch it in the calling code (or somewhere higher up the call chain)
write a generator that encapsulates the break logic and iterate over that instead over the range
By #3 I mean something like this:
def gen(base):
for item in base:
if item%3 == 0:
break
yield i
for i in gen(range(1, 100)):
print "Pass," i
This allows you to put the break with the condition by grouping them into a generator based on the "base" iterator (in this case a range). You then iterate over this generator instead of over the range itself and you get the breaking behavior.
Elaborating BrenBarns answer: break fortunately will not propagate. break is to break the current loop, period. If you want to propagate an event, then you should raise an exception. Although, raising the exception to break the loop is a really ugly way to break loops and a nice way to break your code.
KISS! The simplest would be to check the condition directly in the loop
def my_condition(x):
return x == 4
for i in xrange(100):
if my_condition(i): break
print i
If, for some reason, you want to propagate an exception, then you use it like this
# exception example
for i in xrange(100):
if i == 4: raise Exception("Die!")
print i
As mentioned, it is a really ugly design. Imagine you forget to catch this exception, or you change its type from Exception to MyBreakException and forget to change it somewhere in try/except higher part of the code...
The generator example has its merits, it makes your code more functional style (which I presonally adore)
# generator example
def conditional_generator(n, condition):
for i in xrange(n):
if condition(i):
break
else:
yield i
for i in conditional_generator( 100, my_condition ):
print i
...which is similar to takewhile, mentioned by eumiro
def funcA(i):
if i%3==0:
print "Oh! No!",
print i
return True
else:
return False
for i in range(100):
if funcA(i):
break
print "Pass",
print i
Break won't propagate between functions, you need to put it directly within the loop somewhere.

Can a def function break a while loop?

def CardsAssignment():
Cards+=1
print (Cards)
return break
while True:
CardsAssignment()
Yes, I know that I cannot return break. But how can I break a while loop by the def function? Or my concept is wrong?
No it cannot. Do something like:
def CardsAssignment():
Cards+=1
print (Cards)
if want_to_break_while_loop:
return False
else:
return True
while True:
if not CardsAssignment():
break
A very Pythonic way to do it would be to use exceptions with something like the following:
class StopAssignments(Exception): pass # Custom Exception subclass.
def CardsAssignment():
global Cards # Declare since it's not a local variable and is assigned.
Cards += 1
print(Cards)
if time_to_quit:
raise StopAssignments
Cards = 0
time_to_quit = False
while True:
try:
CardsAssignment()
except StopAssignments:
break
Another, less common approach would be to use a generator function which will return True indicating that it's time to quit calling next() on it:
def CardsAssignment():
global Cards # Declare since it's not a local variable and is assigned.
while True:
Cards += 1
print(Cards)
yield not time_to_quit
Cards = 0
time_to_quit = False
cr = CardsAssignment() # Get generator iterator object.
next(cr) # Prime it.
while next(cr):
if Cards == 4:
time_to_quit = True # Allow one more iteration.
You could have CardsAssignment return True (to continue) or False (to stop) and then have
if not CardsAssignment():
break
or indeed just loop
while CardsAssignment():
If you use a for loop instead of a while, you can cause it to break early by raiseing StopIteration - this is the usual signal for a for loop to finish, and as an exception, it can be nested inside functions as deep as you need and will propagate outward until it is caught. This means you need something to iterate over - and so, you probably want to change your function into a generator:
def cards_assignment():
cards += 1
yield cards
for card in cards_assignment():
print(card)
, in which case instead of doing raise StopIteration, you would just return from the generator and the loop will finish. However, note that this (as well as options having the function return a flag that you test in the loop condition) is subtly different to using break - if you use an else clause on your loop, returning from a generator will trigger it, whereas break in the loop body won't.
def CardsAssignment():
Cards+=1
print (Cards)
if (break_it):
return False
else:
return True
while CardsAssignment():
pass
I would be tempted to just re-factor similar to:
def somefunc():
from random import randint
while True:
r = randint(1, 100)
if r != 42:
yield r
And then you can do things such as:
for cnt, something in enumerate(somefunc(), start=1):
print 'Loop {} is {}'.format(cnt, something)
This allows a possible meaningful value to be returned from somefunc() instead of using it as a "do I break" flag.
This will also allow the following construct:
sf = somefunc()
for something in sf:
if some_condition(something):
break
# other bits of program
for something in sf: # resume...
pass
in 3.5, 3.6 you can
return "break"

Categories