Dictionary infinite loop is exiting unexpectedly - python

I was experimenting with various ways of creating an infinite loop in Python (other than the usual while True), and came up with this idea:
x = {0: None}
for i in x:
del x[i]
x[i+1] = None # Value doesn't matter, so I set it to None
print(i)
On paper, I traced out the way this would infinitely loop:
I loop through the key's value in the dictionary
I delete that entry.
The current counter position in the loop + 1 will be the new key with value None which updates the dictionary.
I output the current counter.
This, in my head, should output the natural numbers in a sort of infinite loop fashion:
0
1
2
3
4
5
.
.
.
I thought this idea was clever, however when I run it on Python 3.6, it outputs:
0
1
2
3
4
Yes, it somehow stopped after 5 iterations. Clearly, there is no base condition or sentinel value in the code block of the loop, so why is Python only running this code 5 times?

There is no guarantee that you will iterate over all your dict entries if you mutate it in your loop. From the docs:
Iterating views while adding or deleting entries in the dictionary may
raise a RuntimeError or fail to iterate over all entries.
You could create an "enumerated" infinite loop similar to your initial attempt using itertools.count(). For example:
from itertools import count
for i in count():
print(i)
# don't run this without some mechanism to break the loop, i.e.
# if i == 10:
# break
# OUTPUT
# 0
# 1
# 2
# ...and so on

In this case, like #benvc wrote, this is not guaranteed to work. But in case you wonder why does it work in C-Python:
The C-Python implementation destroys the dict object after some inserts and copies it to a new space in memory. It does not care about deletions. So when this happens, the loop notices it and breaking with an exception.
Check out this link if you want to read more about this, and many other interesting python internals here.
https://github.com/satwikkansal/wtfpython#-modifying-a-dictionary-while-iterating-over-it

I just tested your code in python2 and python3
python3 output
0,1,2,3,4
python2
0,1,2,3,4,5,6,7
One thing comes to mind that could be going on. Either there is only a certain amount of memory being allocated in your dictionary when you create the first key value and when you delete the key value we do not allocate any memory or deallocate the memory you are just removing the value. Once all the allocated memory is used it exits. Because if you run without that del you will get this error
RuntimeError: dictionary changed size during iteration
So python creates enough memory for that key value and a few more and once it is used up theres no more memory allocated for your dictionary.

As many pointed out, modifying a datastructure during iteration with a for loop is not a good idea. The while loop though does allow that as it re-evaluates its loop condition at each iteration (I'm impressed nobody suggested that as alternative yet). One just has to find the right loop condition. Your script would have to become:
x = {0: None}
while x:
i, _ = x.popitem()
print(i)
# to avoid infinite loop while testing
# if i == 10:
# break
x[i+1] = None
In Python, a dictionary is falsy when it is empty (see docs), so the loop will only stop if at the beginning of an iteration x is empty.
Since the dictionary only has one key-value pair, popitem() should be enough to get that pair and remove it from the dictionary. As the next integer is added right after the dictionary is emptied, the loop condition will never be false when evaluated thereby resulting in an infinite loop.

Related

Removing items from list while iterating over it

There are two separate processes running in Python script. Both interact with a global variable POST_QUEUE = []
Process 1 (P1) adds items to POST_QUEUE every 60 seconds. This can be anywhere from 0 to 50 items at a time.
Process 2 (P2) iterates over POST_QUEUE via a for-loop at set intervals and performs an operation on the list items one at a time. After performing said operation, the process removes the item from the list.
Below is a generalized version of P2:
def Process_2():
for post in POST_QUEUE:
if perform_operation(post):
Print("Success!")
else:
Print("Failure.")
POST_QUEUE.remove(post)
Understandably, I've run into an issue where when removing items from a list that a for-loop is iterating over, it screws up the indexing and terminates the loop earlier than expected (i.e., before it performs the necessary operation on each post and removes it from POST_QUEUE).
Is there a better way to do this than just creating a copy of POST_QUEUE and having P2 iterate over that while removing items from the original POST_QUEUE object? For example:
def Process_2():
POST_QUEUE_COPY = POST_QUEUE[:]
for post in POST_QUEUE_COPY:
if perform_operation(post):
Print("Success!")
else:
Print("Failure.")
POST_QUEUE.remove(post)
Since you do not really need the indexes of the elements I would suggest something like this as an easy solution:
def Process_2():
while len(POST_QUEUE):
if perform_operation(post[0]):
Print("Success!")
else:
Print("Failure.")
POST_QUEUE.remove(post[0])
However this solution has a runtime of O(n^2) for every use of the loop since python needs to move every element in the list on every iteration.
So IMO a better implmentation would be:
def Process_2():
reversed_post_queue = POST_QUEUE[::-1]
while len(reversed_post_queue):
if perform_operation(post[-1]):
Print("Success!")
else:
Print("Failure.")
POST_QUEUE.remove(post[-1])
that way you keep the order (which I suppose is important to you throughout this answer) while only moving the elements of the list once and resulting in a runtime of O(n)
finally, the best implementation IMO is to create or import a queue module
so that you could easily use the list as FIFO.
How about this:
while POST_QUEUE_COPY:
post = POST_QUEUE_COPY.pop(0)
if perform_operation(post):
Print("Success!")
else:
Print("Failure.")
And processes don’t share data, threads do. So unless you are using something like multiprocessing.Manager or some shared-memory construct, I don’t think your current logic would work.
You can loop through your list from right to left. This way, the removal of items will not cause issues to the loop. It's not a good idea in general to remove items from list while looping, but if you need to do it, going from right to left is the best option:
def Process_2():
for i in range(len(POST_QUEUE)-1, -1, -1):
if perform_operation(POST_QUEUE[i]):
Print("Success!")
else:
Print("Failure.")
POST_QUEUE.pop(i)

python How to while loop while true , run multiple functions together

def function():
while True:
...omission...(this function is repeated permanently)
i =0
while i < 4:
driver.execute_script("""window.open("URL")""")
driver.switch_to.window(driver.window_handles[-1])
time.sleep(1)
function()
time.sleep(1)
i += 1 #open new tab and run function.
it doesn't work because while true loop is repeated permanently. Is there any ways to run multiple functions together?
https://imgur.com/a/4SIVekS This picture shows what I want
According to your picture, what you want is to launch the function a set number of times (4?), and run those in parrallel.
On a single core, as is the normal behavior, straight up parallel processing is impossible. You need to access other cores and manage a decentralized processing. while is useless there. I'm worried the level of difficulty is over your current skills, but here we go.
The overall flow that you (probably, depends on the actual memory safety of your functions) need is:
- to create a thread pool with the set number of threads for the number of runs you want.
- indicate the function you need to run
- start them, making sure the start itself is non-blocking.
- ensure one functions's processing doesn't impact another's results. race conditions are a common problem.
- gather results, again, in a non-blocking way.
You can use several methods. I highly recommend you read up a lot on the following documentations.
Threading:
https://docs.python.org/3/library/threading.html
Multiprocessing:
https://docs.python.org/3/library/multiprocessing.html
I don't understand your question because I don't understand what your function is supposed to do.
while True:
will always create an infinite loop. "while" is a command that tells python to loop through the following block so long as the expression following it evaluates to True. True always evaluates to True.
It seems like you want to use a conditional, like you do in "while x < 4".
x < 4
...is an expression that evaluates to true when x is less than 4, and false if x is not less than 4. Everything below the line:
while x < 4:
will then run if x is less than 4, and when it's done running that code, it will go back and evaluate if x is less than 4 again, and if it is, run the code again. To include another while loop inside of that loop, that new loop also needs an expression to evaluate. If you want to evaluate the same expression, write it out:
while x < 4:
# do something
while x < 4:
#do more things
# do even more things
# change x at some point, or else you're in an infinite loop.
However, there's no reason to do that specifically, because you're already doing it. All of the code is only running when x < 4, so checking that condition again right there is redundant, and doing it in another loop doesn't make sense. If the inside loop is also incrementing x, then the outside loop won't loop and doesn't need to increment x.
Also, if you want a function to check a condition based on a variable outside the function, you'll want to pass things to that function.

Is it correct to append to a list while iterating over it?

I see that I can append to a list while iterating over it
lst = [1]
for i in lst:
lst.append(i+1)
print(i)
Am I allowed to make use of this behavior? or is it discouraged? I note that the same can not be said for set
lst = set([1])
for i in lst:
lst.add(i+1)
print(i)
Error: size changed during iteration.
Appending to a list while iterating over it is allowed because lists are ordered so the behavior of appending during iteration is predictable. This makes it useful for retrying failing tasks when all the other tasks in the queue have finished, for example:
tasks = ['task1', 'task2']
for task in tasks:
if task == 'task1':
tasks.append('task1-retry')
print(task)
This outputs:
task1
task2
task1-retry
But sets are not ordered, so adding an item to a set while iterating over it sequentially has an indeterminate effect, and is therefore disallowed.
As others have already said you will create and infinite loop. But you can catch that with the break statement in python: https://docs.python.org/2.0/ref/break.html
But then again if you catch it with a break you could rewrite it into another loop where it stops whenever the condition is fulfilled that you use for the break statement.
I think it will not work because, if list size changed then(?) loop items should change as well, e.g. it will probably become infinite loop or memory access violation.
Better do it that way:
list=[1,2,7,5]
list2=[]
for i in list:
list2.append(i+1)
print(i)
list=list+list2

how can I detect infinite loops in python

I am learning Python 3 and working on an exercise that calls for writing a Python program which simulates/reads a BASIC program as input. I am stuck on writing the part of the Python program that should detect infinite loops. Here is the code I have so far:
def execute(prog):
while True:
location = 0
if prog[location] == len(prog) - 1:
break
return "success"
getT = prog[location].split()
T = len(getT) - 1
location = findLine(prog, T)
visited = [False] * len(prog)
Here, prog is a list of strings containing the BASIC program (strings are in the form of 5 GOTO 30, 10 GOTO 20, etc.).
T is the target string indicated in prog[location].
If the BASIC program has an infinite loop, then my Python program will have an infinite loop. I know that if any line is visited twice, then it loops forever, and my program should return "infinite loop".
A hint given by the tutorial assistant says "initialize a list visited = [False] * len(prog) and change visited[i] to True when prog[i] is visited. Each time through the loop, one value updates in visited[]. Think about how you change a single value in a list. Then think about how you identify which value in visited[] needs to change."
So this is the part I am stuck on. How do I keep track of which strings in prog have been visited/looped through?
I'm not sure I agree that visiting a line twice proves an infinite loop. See the comments under the question. But I can answer the actual question.
Here's the hint:
A hint given by the tutorial assistant says "initialize a list visited = [False] * len(prog) and change visited[i] to True when prog[i] is visited. Each time through the loop, one value updates in visited[]. Think about how you change a single value in a list. Then think about how you identify which value in visited[] needs to change."
This is saying you should have two lists, one that contains the program, and one that contains true/false flags. The second one is to be named visited and initially contains False values.
The Python code is just like the hint says:
visited = [False] * len(prog)
This uses the * list operator, "list repetition", to repeat a length-1 list and make a new list of a longer length.
To change visited[i] to True is simple:
visited[i] = True
Then you can do something like this:
if visited[i]:
print("We have already visited line {}".format(i))
print("Infinite loop? Exiting.")
sys.exit(1)
Note that we are testing for the True value by simply saying if visited[i]:
We could also write if visited[i] == True: but the shorter form is sufficient and is customary in the Python community. This and other customary idioms are documented here: http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html
For a program this small, it's not too bad to keep two lists like this. For larger and complex programs, I prefer to keep everything together in one place. This would use a "class" which you might not have learned yet. Something like this:
class ProgramCode(object):
def __init__(self, statement):
self.code = statement
self.visited = False
prog = []
with open(input_basic_program_file, "rt") as f:
for line in f:
prog.append(ProgramCode(line))
Now instead of two lists, we have a single list where each item is a bit of BASIC code and a visited flag.
P.S. The above shows an explicit for loop that repeatedly uses .append() to add to a list. An experienced Python developer would likely use a "list comprehension" instead, but I wanted to make this as easy to follow as possible.
Here's the list comprehension. Don't worry if it looks weird now; your class will teach this to you eventually.
with open(input_basic_program_file, "rt") as f:
prog = [ProgramCode(line) for line in f]
I know of no automatic way of infinite loop detection in Python, but by using divide and conquer methods and testing individual functions, you can find the offending function or block of code and then proceed to debug further.
If the Python program outputs data, but you never see that output, that's a good indicator you have an infinite loop. You can test all your functions in the repl, and the function that does "not come back" [to the command prompt] is a likely suspect.
You can write output under a debug variable of some sort, to be shut off when everything works. This could be a member variable of a Python class to which your code would have to have access to at any time, or you could have a module-scoped variable like Debug=1 and use debug levels to print varying amounts of debug info, like 1 a little, 2 more, 3, even more, and 4 verbose.
As an example, if you printed the value of a loop counter in a suspected function, then eventually that loop counter would keep printing well beyond the count of data (test records) you were using to test.
Here is a combination I came up with using parts of J. Carlos P.'s answer with the hints that steveha gave and using the hint that the instructions gave:
def execute(prog):
location = 0
visited = [False] * len(prog)
while True:
if location==len(prog)-1:
return "success"
findT = prog[location].split()
T = findT[- 1]
if visited[location]:
return "infinite loop"
visited[location] = True
location = findLine(prog, T)

for or while loop to do something n times [duplicate]

This question already has answers here:
More Pythonic Way to Run a Process X Times [closed]
(5 answers)
Closed 7 months ago.
In Python you have two fine ways to repeat some action more than once. One of them is while loop and the other - for loop. So let's have a look on two simple pieces of code:
for i in range(n):
do_sth()
And the other:
i = 0
while i < n:
do_sth()
i += 1
My question is which of them is better. Of course, the first one, which is very common in documentation examples and various pieces of code you could find around the Internet, is much more elegant and shorter, but on the other hand it creates a completely useless list of integers just to loop over them. Isn't it a waste of memory, especially as far as big numbers of iterations are concerned?
So what do you think, which way is better?
but on the other hand it creates a completely useless list of integers just to loop over them. Isn't it a waste of memory, especially as far as big numbers of iterations are concerned?
That is what xrange(n) is for. It avoids creating a list of numbers, and instead just provides an iterator object.
In Python 3, xrange() was renamed to range() - if you want a list, you have to specifically request it via list(range(n)).
This is lighter weight than xrange (and the while loop) since it doesn't even need to create the int objects. It also works equally well in Python2 and Python3
from itertools import repeat
for i in repeat(None, 10):
do_sth()
python3 & python2
just use range():
for _ in range(n):
# do something n times exactly
The fundamental difference in most programming languages is that unless the unexpected happens a for loop will always repeat n times or until a break statement, (which may be conditional), is met then finish with a while loop it may repeat 0 times, 1, more or even forever, depending on a given condition which must be true at the start of each loop for it to execute and always false on exiting the loop, (for completeness a do ... while loop, (or repeat until), for languages that have it, always executes at least once and does not guarantee the condition on the first execution).
It is worth noting that in Python a for or while statement can have break, continue and else statements where:
break - terminates the loop
continue - moves on to the next time around the loop without executing following code this time around
else - is executed if the loop completed without any break statements being executed.
N.B. In the now unsupported Python 2 range produced a list of integers but you could use xrange to use an iterator. In Python 3 range returns an iterator.
So the answer to your question is 'it all depends on what you are trying to do'!

Categories

Resources