Stop iteration from inside a function - python

I am using an iterator to flexiblily go through a collection. In my function there are several cases in which the function gets a new item and processes them. So there are several cases in which something like this happens:
it = iter(range(10))
while condition:
try:
item = next(it)
item.do_many_different_things()
except StopIteration:
break
And that makes everything extremly messy, so I wanted to move it into a seperate methode. But than I can't use break, because python doesn't know what loop it should break. So far I'm returning a None type, and break the loop if a None was returned. But is there a more elegant solution?

You can return value from the do_many_different_things() function and change the condition variable accordingly, so it breaks out of the while loop as needed.
def func(it):
item = next(it)
res = item.do_many_different_things()
yield res
it = iter(range(1, 10))
condition = True
while condition:
for item in func(it):
condition = item
This will run all elements from 1..9, because they are all truthy. If you start this with the regular range(10) it would stop on the first element since it's 0.
Once the method return False, the while loop breaks.

I don't know what your code, nested loops and items are, so I will just show you how to break out from nested loops spread across different functions. I will also show you, how you can differentiate three cases:
1. Your item.do_many_different_things() method wants to break
2. You ran out of items
3. Your condition evaluates to False
This is purely educational to show you some Python features which you might find useful, not necessarily in this exact combination.
from __future__ import print_function
# I'm on Python 3 - you will need the above line on Python 2
# I don't know what your code is supposed to do so I'll just generate random integers
from random import Random
r = Random()
r.seed()
class BreakOutNested(Exception): pass
class Item(object):
def do_many_different_things(self):
x = r.randint(0, 50)
if x == 50:
raise BreakOutNested()
self.x = x
def iterator_0(item):
for i in range(5):
item.do_many_different_things()
yield i
def iterator_1(items):
for item in items:
for i in iterator_0(item):
item.i = i
yield item
items = iterator_1(Item() for i in range(5))
x = 50
try:
while x != 0:
item = next(items)
print(item.i, item.x)
x = item.x
except BreakOutNested:
print('Broke out from many loops with exception trick')
except StopIteration:
print('Simply ran out of items')
else:
print('Got x == 0')
Run this a couple of times as the exit scenario is random.

Related

Make the built in function len() in python

I'm trying to make a function to return the length of a list in python.
Code
def get_len(arr):
ind = 0
try:
x = arr[ind]
while x is not IndexError():
ind += 1
x = arr[ind]
except IndexError:
return ind
print(get_len([1, 2, 3, "one", "two"]))
Questions
Is it wrong to use try / except to get the length ?
Are there other ways to do it ?
Which resources to see the implementation of built-in function len ?
The while x is not IndexError() part is unnecessary (x will never equal an IndexError()). Here's a simpler version:
def get_len(arr):
ind = 0
while True:
try:
arr[ind]
except IndexError:
return ind
ind += 1
print(get_len([1, 2, 3, "one", "two"]))
The statement arr[ind] is sufficient to check for an error; if ind is equal to or greater than len(arr), the subscript operator will raise (not return) an IndexError.
The actual built-in function is typically not going to be one that's defined in Python itself. See for example the CPython implementation: https://github.com/python/cpython/blob/main/Objects/listobject.c#L429
Note that the real len() function is much faster than a reimplemented version can be because the list already "knows" its length and can return it instantly without having to count up the items one by one.
It will work, but it's just by accident. The while condition will always be true, because every call to IndexError() returns a new object, which is not the same object as any value in the list. You can replace that with just while True:.
The try/except is what detects the index error when you reach the end of the list.
There's no need to execute x = arr[ind] multiple times. Just put that inside the loop before incrementing ind.
def get_len(arr):
ind = 0
while True:
try:
x = arr[ind]
except IndexError:
return ind
ind += 1
Just to provide an alternative way of getting the length of a list or even arbitrarily long iterables, I've seen some library that had something like this to achieve that:
from collections import deque
def get_len(it):
try:
res, _ = deque(enumerate(it, 1), 1).pop()
except IndexError:
return 0
return res

How to solve StopIteration error in Python?

I have just read a bunch of posts on how to handle the StopIteration error in Python, I had trouble solving my particular example.I just want to print out from 1 to 20 with my code but it prints out error StopIteration. My code is:(I am a completely newbie here so please don't block me.)
def simpleGeneratorFun(n):
while n<20:
yield (n)
n=n+1
# return [1,2,3]
x = simpleGeneratorFun(1)
while x.__next__() <20:
print(x.__next__())
if x.__next__()==10:
break
Any time you use x.__next__() it gets the next yielded number - you do not check every one yielded and 10 is skipped - so it continues to run after 20 and breaks.
Fix:
def simpleGeneratorFun(n):
while n<20:
yield (n)
n=n+1
# return [1,2,3]
x = simpleGeneratorFun(1)
while True:
try:
val = next(x) # x.__next__() is "private", see #Aran-Frey comment
print(val)
if val == 10:
break
except StopIteration as e:
print(e)
break
First, in each loop iteration, you're advancing the iterator 3 times by making 3 separate calls to __next__(), so the if x.__next__()==10 might never be hit since the 10th element might have been consumed earlier. Same with missing your while condition.
Second, there are usually better patterns in python where you don't need to make calls to next directly. For example, if you have finite iterator, use a for loop to automatically break on StopIteration:
x = simpleGeneratorFun(1)
for i in x:
print i

Are infinite for loops possible in Python? [duplicate]

This question already has answers here:
Looping from 1 to infinity in Python
(8 answers)
Closed 5 months ago.
The community reviewed whether to reopen this question 4 months ago and left it closed:
Original close reason(s) were not resolved
Is it possible to get an infinite loop in for loop?
My guess is that there can be an infinite for loop in Python. I'd like to know this for future references.
You can use the second argument of iter(), to call a function repeatedly until its return value matches that argument. This would loop forever as 1 will never be equal to 0 (which is the return value of int()):
for _ in iter(int, 1):
pass
If you wanted an infinite loop using numbers that are incrementing you could use itertools.count:
from itertools import count
for i in count(0):
....
The quintessential example of an infinite loop in Python is:
while True:
pass
To apply this to a for loop, use a generator (simplest form):
def infinity():
while True:
yield
This can be used as follows:
for _ in infinity():
pass
Yes, use a generator that always yields another number:
Here is an example
def zero_to_infinity():
i = 0
while True:
yield i
i += 1
for x in zero_to_infinity():
print(x)
It is also possible to achieve this by mutating the list you're iterating on, for example:
l = [1]
for x in l:
l.append(x + 1)
print(x)
In Python 3, range() can go much higher, though not to infinity:
import sys
for i in range(sys.maxsize**10): # you could go even higher if you really want but not infinity
pass
Here's another solution using the itertools module:
import itertools
for _ in itertools.repeat([]): # return an infinite iterator
pass
It's also possible to combine built-in functions iter (see also this answer) and enumerate for an infinite for loop which has a counter:
for i, _ in enumerate(iter(bool, True)):
input(i)
Which prints:
0
1
2
3
4
...
This uses iter to create an infinite iterator and enumerate provides the counting loop variable. You can even set a start value other than 0 with enumerate's start argument:
for i, _ in enumerate(iter(bool, True), start=42):
input(i)
Which prints:
42
43
44
45
46
...
Python infinite for loop
Well, there are a few ways, but the easiest one I found is to Iterate over a list
To understand this you must be knowing about:
python basics
python loops
python lists ( and also its append() function
The syntax is something like this...
l = ['#'] # Creating a list
for i in l: # Iterating over the same list
print(l)
l.append(i) # Appending the same element
With every iteration, the an element gets appended to the list. This way the loop never stops iterating.
Happy coding : )
While there have been many answers with nice examples of how an infinite for loop can be done, none have answered why (it wasn't asked, though, but still...)
A for loop in Python is syntactic sugar for handling the iterator object of an iterable an its methods. For example, this is your typical for loop:
for element in iterable:
foo(element)
And this is what's sorta happening behind the scenes:
iterator = iterable.__iter__()
try:
while True:
element = iterator.next()
foo(element)
except StopIteration:
pass
An iterator object has to have, as it can be seen, anextmethod that returns an element and advances once (if it can, or else it raises a StopIteration exception).
So every iterable object of which iterator'snextmethod does never raise said exception has an infinite for loop. For example:
class InfLoopIter(object):
def __iter__(self):
return self # an iterator object must always have this
def next(self):
return None
class InfLoop(object):
def __iter__(self):
return InfLoopIter()
for i in InfLoop():
print "Hello World!" # infinite loop yay!
we can actually have a for infinite loop
list = []
for i in list:
list.append(i)
print("Your thing")
i found a way without using yield or a while loop.
my python version is python 3.10.1
x = [1]
for _ in x:
x.append(1)
print('Hello World!')
if you need loop count, you can use i+1:
x = [1]
for i in x:
x.append(i+1)
print(f'Hello {i}')
you should know that this is not really an "infinite" loop.
because as the loop runs, the list grows and eventually, you will run out of ram.
Best way in my opinion:
for i in range(int(1e18)):
...
The loop will run for thousands of years
You can configure it to use a list. And append an element to the list everytime you iterate, so that it never ends.
Example:
list=[0]
t=1
for i in list:
list.append(i)
#do your thing.
#Example code.
if t<=0:
break
print(t)
t=t/10
This exact loop given above, won't get to infinity. But you can edit the if statement to get infinite for loop.
I know this may create some memory issues, but this is the best that I could come up with.
n = 0
li = [0]
for i in li:
n += 1
li.append(n)
print(li)
In the above code, we iterate over the list (li).
So in the 1st iteration, i = 0 and the code in for block will run, that is li will have a new item (n+1) at index 1 in this iteration so our list becomes [ 0, 1 ]
Now in 2nd iteration, i = 1 and new item is appended to the li (n+1), so the li becomes [0, 1, 2]
In 3rd iteration, i = 2, n+1 will be appended again to the li, so the li becomes [ 0, 1, 2, 3 ]
This will keep on going as in each iteration the size of list is increasing.
The other solutions solutions have a few issues, such as:
consuming a lot of memory which may
cause memory overflow
consuming a lot of processor power.
creating deadlock.
using 3rd party library
Here is an answer, which will overcome these problems.
from asyncio import run, sleep
async def generator():
while True:
await sleep(2)
yield True
async def fun():
async for _ in generator():
print("Again")
if __name__ == '__main__':
run(fun())
In case you want to do something that will take time, replace sleep with your desired function.
i'm newbie in python but try this
for i in range(2):
# your code here
i = 0
can improve this code
In Python 2.x, you can do:
my_list = range(10)
for i in my_list:
print "hello python!!"
my_list.append(i)

Search function python

Hi Im trying to create a search function in python, that goes through a list and searches for an element in it.
so far ive got
def search_func(list, x)
if list < 0:
return("failure")
else:
x = list[0]
while x > list:
x = list [0] + 1 <---- how would you tell python to go to the next element in the list ?
if (x = TargetValue):
return "success"
else
return "failure"
Well, you current code isn't very Pythonic. And there are several mistakes! you have to use indexes to acces an element in a list, correcting your code it looks like this:
def search_func(lst, x):
if len(lst) <= 0: # this is how you test if the list is empty
return "failure"
i = 0 # we'll use this as index to traverse the list
while i < len(lst): # this is how you test to see if the index is valid
if lst[i] == x: # this is how you check the current element
return "success"
i += 1 # this is how you advance to the next element
else: # this executes only if the loop didn't find the element
return "failure"
... But notice that in Python you rarely use while to traverse a list, a much more natural and simpler approach is to use for, which automatically binds a variable to each element, without having to use indexes:
def search_func(lst, x):
if not lst: # shorter way to test if the list is empty
return "failure"
for e in lst: # look how easy is to traverse the list!
if e == x: # we no longer care about indexes
return "success"
else:
return "failure"
But we can be even more Pythonic! the functionality you want to implement is so common that's already built into lists. Just use in to test if an element is inside a list:
def search_func(lst, x):
if lst and x in lst: # test for emptiness and for membership
return "success"
else:
return "failure"
Are you saying you want to see if an element is in a list? If so, there is no need for a function like that. Just use in:
>>> lst = [1, 2, 3]
>>> 1 in lst
True
>>> 4 in lst
False
>>>
This method is a lot more efficient.
If you have to do it without in, I suppose this will work:
def search_func(lst, x):
return "success" if lst.count(x) else "failure"
you dont need to write a function for searching, just use
x in llist
Update:
def search_func(llist,x):
for i in llist:
if i==x:
return True
return False
You are making your problem more complex, while solving any problem just think before starting to code. You are using while loops and so on which may sometimes becomes an infinite loop. You should use a for loop to solve it. This is better than while loop. So just check which condition helps you. That's it you are almost done.
def search_func(lst,x):
for e in lst: #here e defines elements in the given list
if e==x: #if condition checks whether element is equal to x
return True
else:
return False
def search(query, result_set):
if isinstance(query, str):
query = query.split()
assert isinstance(query, list)
results = []
for i in result_set:
if all(quer.casefold() in str(i).casefold() for quer in query):
results.append(i)
return results
Works best.

Searching values of a list in another List using Python

I'm a trying to find a sublist of a list. Meaning if list1 say [1,5] is in list2 say [1,4,3,5,6] than it should return True. What I have so far is this:
for nums in l1:
if nums in l2:
return True
else:
return False
This would be true but I'm trying to return True only if list1 is in list2 in the respective order. So if list2 is [5,2,3,4,1], it should return False. I was thinking along the lines of comparing the index values of list1 using < but I'm not sure.
try:
last_found = -1
for num in L1:
last_found = L2.index(num, last_found + 1)
return True
except ValueError:
return False
The index method of list L2 returns the position at which the first argument (num) is found in the list; called, like here, with a second arg, it starts looking in the list at that position. If index does not find what it's looking for, it raises a ValueError exception.
So, this code uses this approach to look for each item num of L1, in order, inside L2. The first time it needs to start looking from position 0; each following time, it needs to start looking from the position just after the last one where it found the previous item, i.e. last_found + 1 (so at the start we must set last_found = -1 to start looking from position 0 the first time).
If every item in L1 is found this way (i.e. it's found in L2 after the position where the previous item was found), then the two lists meet the given condition and the code returns True. If any item of L1 is ever not-found, the code catches the resulting ValueError exception and just returns False.
A different approach would be to use iterators over the two lists, that can be formed with the iter built-in function. You can "advance" an iterator by calling built-in next on it; this will raise StopIteration if there is no "next item", i.e., the iterator is exhausted. You can also use for on the iterator for a somewhat smoother interface, where applicable. The low-level approach using the iter/next idea:
i1 = iter(L1)
i2 = iter(L2)
while True:
try:
lookfor = next(i1)
except StopIteration:
# no more items to look for == all good!
return True
while True:
try:
maybe = next(i2)
except StopIteration:
# item lookfor never matched == nope!
return False
if maybe == lookfor:
break
or, a bit higher-level:
i1 = iter(L1)
i2 = iter(L2)
for lookfor in i1:
for maybe in i2:
if maybe == lookfor:
break
else:
# item lookfor never matched == nope!
return False
# no more items to look for == all good!
return True
In fact, the only crucial use of iter here is to get i2 -- having the inner loop as for maybe in i2 guarantees the inner loop won't start looking from the beginning every time, but, rather, it will keep looking where it last left off. The outer loop might as well for for lookfor in L1:, since it has no "restarting" issue.
Key, here, is the else: clause of loops, which triggers if, and only if, the loop was not interrupted by break but rather exited naturally.
Working further on this idea we are again reminded of the in operator, which also can be made to continue where it last left off simply by using an iterator. Big simplification:
i2 = iter(L2)
for lookfor in L1:
if lookfor not in i2:
return False
# no more items to look for == all good!
return True
But now we recognize that is exactly the patter abstracted by the short-circuiting any and all built-in "short-circuiting accumulator" functions, so...:
i2 = iter(L2)
return all(lookfor in i2 for lookfor in L1)
which I believe is just about as simple as you can get. The only non-elementary bit left here is: you need to use an iter(L2) explicitly, just once, to make sure the in operator (intrinsically an inner loop) doesn't restart the search from the beginning but rather continues each time from where it last left off.
This question looks a bit like homework and for this reason I'd like to take the time and discuss what may be going wrong with the snippet shown in the question.
Although you are using a word in its plural form, for the nums variable, you need to understand that Python will use this variable to store ONE item from l1 at a time, and go through the block of code in this "for block", one time for each different item.
The result of your current snippet will therefore be to exit upon the very first iteration, with either True or False depending if by chance the first items in the list happen to match.
Edit: Yes, A1, exactly as you said: the logic exits with True after the first iteration. This is because of the "return" when nums is found in l2.
If you were to do nothing in the "found" case, the loop the logic would proceed with finishing whatever logic in the block (none here) and it would then start the next iteration. Therefore it would only exit with a "False" return value, in the case when an item from l1 is not found l2 (indeed after the very first such not-found item). Therefore your logic is almost correct (if it were to do nothing in the "found case"), the one thing missing would be to return "True", systematically after the for loop (since if it didn't exit with a False value within the loop, then all items of l2 are in l1...).
There are two ways to modify the code so it does nothing for the "found case".
- by using pass, which is a convenient way to instruct Python to do nothing; "pass" is typically used when "something", i.e. some action is syntactically required but we don't want anything done, but it can also be used when debugging etc.
- by rewriting the test as a "not in" instead
if nums not in l2:
return False
#no else:, i.e. do nothing at all if found
Now... Getting into more details.
There may be a flaw in your program (with the suggested changes), that is that it would consider l1 to be a sublist of l2, even if l1 had say 2 items with value say 5 whereby l2 only had one such value. I'm not sure if that kind of consideration is part of the problem (possibly the understanding is that both lists are "sets", with no possible duplicate items). If duplicates were allowed however, you would have to complicate the logic somewhat (a possible approach would be to intitially make a copy of l2 and each time "nums" is find in the l2 copy, to remove this item.
Another consideration is that maybe a list can only be said to be a sublist if its items are found the same order as the items in the other list... Again it all depends on the way the problem is defined... BTW some of the solutions proposed, like Alex Martelli's are written in such fashion because they solve the problem in a way that the order of items with the lists matter.
I think this solution is the fastest, since it iterates only once, albeit on the longer list and exits before finishing the iteration if a match is found. (Edit: However, it is not as succinct or as fast as Alex's latest solution)
def ck(l1,l2):
i,j = 0,len(l1)
for e in l2:
if e == l1[i]:
i += 1
if i == j:
return True
return False
An improvement was suggested by Anurag Uniyal (see comment) and is reflected in the showdown below.
Here are some speed results for a range of list size ratios (List l1 is a 10-element list containing random values from 1-10. List l2 ranges from 10-1000 in length (and also contain random values from 1-10).
Code that compares run times and plots the results:
import random
import os
import pylab
import timeit
def paul(l1,l2):
i = 0
j = len(l1)
try:
for e in l2:
if e == l1[i]:
i += 1
except IndexError: # thanks Anurag
return True
return False
def jed(list1, list2):
try:
for num in list1:
list2 = list2[list2.index(num):]
except: return False
else: return True
def alex(L1,L2): # wow!
i2 = iter(L2)
return all(lookfor in i2 for lookfor in L1)
from itertools import dropwhile
from operator import ne
from functools import partial
def thc4k_andrea(l1, l2):
it = iter(l2)
try:
for e in l1:
dropwhile(partial(ne, e), it).next()
return True
except StopIteration:
return False
ct = 100
ss = range(10,1000,100)
nms = 'paul alex jed thc4k_andrea'.split()
ls = dict.fromkeys(nms)
for nm in nms:
ls[nm] = []
setup = 'import test_sublist as x'
for s in ss:
l1 = [random.randint(1,10) for i in range(10)]
l2 = [random.randint(1,10) for i in range(s)]
for nm in nms:
stmt = 'x.'+nm+'(%s,%s)'%(str(l1),str(l2))
t = timeit.Timer(setup=setup, stmt=stmt).timeit(ct)
ls[nm].append( t )
pylab.clf()
for nm in nms:
print len(ss), len(ls[nm])
pylab.plot(ss,ls[nm],label=nm)
pylab.legend(loc=0)
pylab.xlabel('length of l2')
pylab.ylabel('time')
pylab.savefig('cmp_lsts.png')
os.startfile('cmp_lsts.png')
results:
This should be easy to understand and avoid corner case nicely as you don't need to work with indexes:
def compare(l1, l2):
it = iter(l2)
for e in l1:
try:
while it.next() != e: pass
except StopIteration: return False
return True
it tries to compare each element of l1 to the next element in l2.
if there is no next element (StopIteration) it returns false (it visited the whole l2 and didn't find the current e) else it found it, so it returns true.
For faster execution it may help to put the try block outside the for:
def compare(l1, l2):
it = iter(l2)
try:
for e in l1:
while it.next() != e: pass
except StopIteration: return False
return True
I have a hard time seeing questions like this and not wishing that Python's list handling was more like Haskell's. This seems a much more straightforward solution than anything I could come up with in Python:
contains_inorder :: Eq a => [a] -> [a] -> Bool
contains_inorder [] _ = True
contains_inorder _ [] = False
contains_inorder (x:xs) (y:ys) | x == y = contains_inorder xs ys
| otherwise = contains_inorder (x:xs) ys
The ultra-optimized version of Andrea's solution:
from itertools import dropwhile
from operator import ne
from functools import partial
def compare(l1, l2):
it = iter(l2)
try:
for e in l1:
dropwhile(partial(ne, e), it).next()
return True
except StopIteration:
return False
This can be written even more functional style:
def compare(l1,l2):
it = iter(l2)
# any( True for .. ) because any([0]) is False, which we don't want here
return all( any(True for _ in dropwhile(partial(ne, e), it)) for e in l1 )
I have a feeling this is more intensive than Alex's answer, but here was my first thought:
def test(list1, list2):
try:
for num in list1:
list2 = list2[list2.index(num):]
except: return False
else: return True
Edit: Just tried it. His is faster. It's close.
Edit 2: Moved try/except out of the loop (this is why others should look at your code). Thanks, gnibbler.

Categories

Resources