I am trying to create a generator that will return the natural numbers in order. This is used to enumerate another generator which will exit upon StopIteration, which seems like the easiest way to do it. However, I cannot find an idiomatic way of creating this generator:
def numbers():
i = 0
while True:
yield i
i += 1
q = Queue.Queue()
for i in numbers():
try:
q.put((i, my_generator.next()))
except StopIteration:
break
This does work, but it seems unpythonic to use a while True: in this way.
Is there a standard library function to iterate over the natural numbers?
To answer your literal question, use itertools.count(). It'll count up from a start value by step, into infinity.
That said, it seems what you actually want to do is this:
for idx, item in enumerate(my_generator):
q.put((idx, item))
You have a couple easy options.
Use a large range():
q = Queue.Queue()
for i in range(10**10):
try:
q.put((i, my_generator.next()))
except StopIteration:
break
Or simply enumerate() the generator you're actually interested in:
q = Queue.Queue()
for item in enumerate(my_generator):
q.put(item)
Related
This question already has answers here:
Loop over empty iterator does not raise exception
(3 answers)
Closed 2 years ago.
I want to log the number of things in an iterator (not trying to catch an empty iterator, like this question), after I've iterated over it and done something else for each thing.
My instinct here is to use the enumerate built-in:
def foo_yielder():
to_yield = ['foo', 'bar']
for x in to_yield:
yield x
for i, x in enumerate(foo_yielder(), start=1):
print(x)
# Now log how many things we printed
print(f"We logged {i} things")
But if the iterator has nothing to yield, i never gets defined.
If this were Javascript I'd do:
`We logged ${i || 0} things`
Taking lessons from C, we could do:
i = 0
for x in foo_yielder():
print(x)
i = i + 1
print("We logged " + i + " things.")
But that seems incredibly lame somehow.
Is there an elegant thing I could do here?
The easiest thing would be to add a i = 0 right before the enumerate loop.
If what bothers is the i = 0 additional line being ugly, you can try the following way:
Add a "dummy" iterator and chain it to your iterator. Then, reset the enumerate to count from 0 (as the default). This way, you assure that the enumerate will iterate on at least one item (and set i to 0), and then count the actual iterator from 1:
from itertools import chain
for i, x in enumerate(chain([0], foo_yielder())):
pass
print(f"We logged {i} things")
This then offers another problem of x always being the dummy on the first iteration. You could handle that with conditions inside the loop, but that would make the code more complicated.
Lastly, you could wrap the final printing with a try/except:
try:
print(f"We logged {i} things")
except NameError:
print(f"We logged 0 things")
But again, that is just more code, instead of a simple i = 0.
Setting this up to enumerate over an empty list, the else clause on the for loop will always execute after the loop has finished, checking for the existence of i in the current namespace. The interstitial print statement isn't really necessary. I don't know if it's exactly elegant, but it's a nice use-case for the for-else idiom.
for i, _ in enumerate([], start=1):
pass # do whatever you want here
else:
if "i" in dir():
print(f"Found {i} instances")
else:
print(f"Found instances")
This does, of course, presume that you haven't used i as a variable elsewhere
And of course, slightly cleaner (DRY, and all that)
for i, _ in enumerate([], start=1):
pass # do whatever you want here
else:
if "i" not in dir():
i = 0
print(f"We logged {i} things")
Why must I use a variable to obtain next() values from a Python generator?
def my_gen():
i = 0
while i < 4:
yield 2 * i
i += 1
#p = my_gen()
#for i in range(4):
# print(next(p))
##for i in range(4):
## print(next(my_gen()))
In the above, the # block works, while the ## block returns 4 copies of the first "yield."
print(next(my_gen()))
Each time this runs, you're calling next() on a separate (and brand-new) generator returned by my_gen().
If you want to call next() on the same generator more than once, you'll need some way to keep that generator around for long enough to reuse it.
just gonna add my $0.02...
this may help clear up some misconceptions:
def function_that_creates_a_generator(n):
while n>0:
yield n
n - 1
# this
for x in function_that_creates_a_generator(5):
print(x)
#is almost exactly the same as this
generator = function_that_creates_a_generator(5)
while True:
try:
x = generator.next() #same as: x = next(generator)
except StopIteration:
break
print(x)
For loops are really just a prettier way of writing a while loop that continually asks an iterable object for its next element. when you ask an iterable for another object and it has none, it will raise a StopIteration exception that is automatically caught in for loops; signaling a break from the loop. In the while loop we simply break these things out individually instead of having them hidden by the interpreter.
Say I have an iterable and want to skip elements as long as the elements match a particular predicate. I want to affect the current iterator, not return a new one.
I could simply do this:
# untested, just for explanation
e = next(iterable)
while True:
if something(e):
e = next(iterable)
else:
break
But is there a built-in function for this, or some common idiom?
This is the basic use-case of itertools.dropwhile.
Why not just
e = next(iterable)
while something(e):
e = next(iterable)
Also, why "I want to affect the current iterator, not return a new one."? I can't think of a scenario when you would need that. If you allow for a new iterator to wrap the current one, itertools.dropwhile that wim suggests is much more Pythonic (and readable).
Your example just plain stops after the first false result, so this does the same:
>>> it = iter(range(100000)) # just constructing _an_ iterator
>>> next(itertools.filterfalse(lambda i: i != 12, it))
12
I'm not clear on what - exactly - "I want to affect the current iterator, not return a new one" means, but do note that it itself has advanced:
>>> next(it)
13
Having a bit of trouble with while loops. I understand this basic for loop runs through whatever is passed into the function but how would I change the for loop to a while loop? Thought it would be as easy as changing the for to while but apparently not so.
def print_data(items):
for item in items:
print(item)
You can do something like this to have the same printing functionality with a while loop:
def print_data(items):
i = 0
while i < len(items):
print items[i]
i += 1
Here is a while loop version that works by constructing an iterator and manually walking it. It works regardless of whether the input is a generator or a list.
def print_data(items):
it = iter(items)
while True:
try:
print next(it)
except StopIteration:
break
print_data([1,2,3])
One option, that works on both lists and generators, is to construct an iterator and then use Python's built-in next function. When the iterator reaches the end, the next function will raise a StopIteration exception, which you can use to break the loop:
def print_data(items):
it = iter(items)
while True:
try:
print next(it)
except StopIteration:
break
print_data(['a', 'b', 'c'])
print_data(('a', 'b', 'c'))
There's more about the next built-in function and iterators in the docs.
If you are learning Python:
If you need to iterate over an iterable (a list, a generator, a string etc.. in short and not precise words something that contains things and can "give" those things one by one..) you better use for.
In Python for was made for iterables, so you don't need a while.
If you are learning programming in general:
while needs a condition to be satisfied to keep looping, you create your own condition making a counter that will increment every loop, and making the while loop go while this counter is less or equal to the lenght of your items, as showed in Mathias711's answer.
The for-loop you are using is iterating through a so called iterator.
This means to walk through iterable objects (lists, generators, dicts,...) and return the next item from the iterator which is returned by the built-in function [next()][2]. If there is no item left, calling this function will raise an so called StopIteration error which causes to stop iteration.
So the pythonic way to iterate througth iteratable objects is in fact using the for-loop you provided in your question. However, if you really want to use a while loop (which at least in general is not recommended at all) you have to iterate using a try-except-block and handle the StopIteration error raised if no item is left.
def iterate_manually(items):
# convert items list into iterator
iterator = iter(items)
while True:
try:
print(next(iterator))
# handle StopIteration error and exit while-loop
except StopIteration:
break
iterate_manually(['foo', 'bar', 'baz'])
You can try this
def print_data(items):
i =0
while items:
if i < len(items):
print items[i]
i = i+1
else:
break
The question is use iterator to find the num from list who is greater than the nums in left and right
For example, select([0,1,0,2,0,3,0,4,0]) return [1,2,3,4]
My first try is
def select(iterable):
answer = []
it = iter(iterable)
try:
v2 = next(it)
while True:
v1,v2= v2,next(it)
if v2>v1 and v2>next(it):
answer.append(v2)
except StopIteration:
pass
return answer
This code fails.
I think the next(it) in the while loop would be the same iterator,but the next() still iter next one in the code.
then I change the code to below one, it works.
try:
v1,v2,v3 = next(it),next(it),next(it)
while True:
if v2>v1 and v2>v3:
answer.append(v2)
v1,v2,v3 = v2,v3,next(it)
except StopIteration:
pass
Can someone explain what is difference happen here?
There are two issues with the first snippet:
Every time you call next(it), it advances the iterator. You have to store the returned value if you want to access it more than once. Calling next(it) again won't do that; it will advance the iterator yet again.
Even if the first point weren't an issue, the following would still be problematic:
if v2>v1 and v2>next(it):
The issue here is short-circuit evaluation. Depending on whether v2>v1, this may or may not advance the iterator.
The error lies in this line:
if v2>v1 and v2>next(it):
Your calling next(it), but you don't store the return value. You just compare it to the v2. So the value gets skipped.
edit:
Btw, if you compare multiple values, the following comparison is much more cleaner:
if v1 < v2 > v3: