Related
Here's a simple iterator through the characters of a string.
class MyString:
def __init__(self,s):
self.s = s
self._ix = 0
def __iter__(self):
return self
def __next__(self):
try:
item = self.s[self._ix]
except IndexError:
self._ix = 0
raise StopIteration
self._ix += 1
return item
string = MyString('abcd')
iter1 = iter(string)
iter2 = iter(string)
print(next(iter1))
print(next(iter2))
Trying to get this iterator to function like it should. There are a few requirements. First, the __next__ method MUST raise StopIteration and multiple iterators running at the same time must not interact with each other.
I accomplished objective 1, but need help on objective 2. As of right now the output is:
'a'
'b'
When it should be:
'a'
'a'
Any advice would be appreciated.
Thank you!
MyString acts as its own iterator much like a file object
>>> f = open('deleteme', 'w')
>>> iter(f) is f
True
You use this pattern when you want all iterators to affect each other - in this case advancing through the lines of a file.
The other pattern is to use a separate class to iterate much like a list whose iterators are independent.
>>> l = [1, 2, 3]
>>> iter(l) is l
False
To do this, move the _ix indexer to a separate class that references MyString. Have MyString.__iter__ create an instance of the class. Now you have a separate indexer per iterator.
class MyString:
def __init__(self,s):
self.s = s
def __iter__(self):
return MyStringIter(self)
class MyStringIter:
def __init__(self, my_string):
self._ix = 0
self.my_string = my_string
def __iter__(self):
return self
def __next__(self):
try:
item = self.my_string.s[self._ix]
except IndexError:
raise StopIteration
self._ix += 1
return item
string = MyString('abcd')
iter1 = iter(string)
iter2 = iter(string)
print(next(iter1))
print(next(iter2))
Your question title asks how to get iterators, plural, to not communicate with each other, but you don't have multiple iterators, you only have one. If you want to be able to get distinct iterators from MyString, you can add a copy method:
class MyString:
def __init__(self,s):
self.s = s
self._ix = 0
def __iter__(self):
return self
def __next__(self):
try:
item = self.s[self._ix]
except IndexError:
self._ix = 0
raise StopIteration
self._ix += 1
return item
def copy(self):
return MyString(self.s)
string = MyString('abcd')
iter1 = string.copy()
iter2 = string.copy()
print(next(iter1))
print(next(iter2))
I have a list with string elements, and in the end I want to recieve:
a hello
b hello
c hello
d hello
And I've got this code:
list=['a','b','c','d']
class Iterator:
def __init__(self, start, end):
self.start=start
self.end=end
def __iter__(self):
return self
def __next__(self):
self.start += ' hello'
if self.start == list[-1]:
raise StopIterration
return self.start
if __name__ == '__main__':
for item in Iterator(list[0], list[-1]):
print(item)
However, the methond __next__ CANNOT MOVE FROM list[0] to list[1], and the python began to be crazy and add billion of "hello" to the list[0], and can't even stop the program, so it's the hell's loop now.
Problems are:
Adding billon of "hello" to list[0], not moving to list[1].
Doesn't finish the program at all, despite I wrote what is a condition for finish.
Your instance of Iterator isn't tied to the list at all; it's irrelevant that you used the list to create the instance; Iterator.__init__ only saw two string values.
__init__ needs a reference to the list itself for use by __next__. Further, hello is something you append to the return value of __next__, not something you need to append to internal state every time you call __next__.
list=['a','b','c','d']
class Iterator:
def __init__(self, lst):
self.lst = lst
self.start = 0
def __iter__(self):
return self
def __next__(self):
try:
value = self.lst[self.start]
except IndexError:
raise StopIteration
self.start += 1
return value + ' hello'
if __name__ == '__main__':
for item in Iterator(list):
print(item)
I defined a class that holds a list collection of objects, and defined the __iter__ and __next__ methods to make it for loopable. The collection here, is a Deck class that holds a list of Card objects.
Code:
import random
class Card:
#staticmethod
def get_ranks():
return ("A", "K", "Q", "J", "10", "9", "8", "7", "6", "5", "4", "3", "2") # A is highest, 2 is lowest
#staticmethod
def get_suites():
return ("H", "D", "S", "C")
def __init__(self, suite, rank):
if suite not in Card.get_suites():
raise Exception("Invalid suite")
if rank not in Card.get_ranks():
raise Exception("Invalid rank")
self.suite = suite
self.rank = rank
def __lt__(self, card2):
self_rank = Card.get_ranks().index(self.rank)
card2_rank = Card.get_ranks().index(card2.rank)
return self_rank > card2_rank
def __le__(self, card2):
self_rank = Card.get_ranks().index(self.rank)
card2_rank = Card.get_ranks().index(card2.rank)
return self_rank >= card2_rank
def __gt__(self, card2):
self_rank = Card.get_ranks().index(self.rank)
card2_rank = Card.get_ranks().index(card2.rank)
return self_rank < card2_rank
def __ge__(self, card2):
self_rank = Card.get_ranks().index(self.rank)
card2_rank = Card.get_ranks().index(card2.rank)
return self_rank <= card2_rank
def __eq__(self, card2):
self_rank = Card.get_ranks().index(self.rank)
card2_rank = Card.get_ranks().index(card2.rank)
return self_rank == card2_rank
def __ne__(self, card2):
self_rank = Card.get_ranks().index(self.rank)
card2_rank = Card.get_ranks().index(card2.rank)
return self_rank != card2_rank
def __str__(self):
return(self.rank + self.suite)
def __repr__(self):
return str(self)
class Deck:
def __init__(self):
self.contents = [Card(suite, rank) for suite in Card.get_suites() for rank in Card.get_ranks()]
random.shuffle(self.contents)
self.index = 0
def __len__(self):
return len(self.contents)
def __iter__(self):
return self
def __next__(self):
if self.index == len(self.contents):
raise StopIteration
item = self.contents[self.index]
self.index += 1
return item
def pick_card(self):
choice = random.randrange(len(self))
card = self.contents.pop(choice)
return card
def return_card_and_shuffle(self, card):
self.contents.append(card)
random.shuffle(self.contents)
def __str__(self):
dstr = ''
for card in self:
dstr += str(card) + ", "
return "{} cards: ".format(len(self)) + dstr[:-2]
def deal_bookends(deck):
card1 = deck.pick_card()
card2 = deck.pick_card()
if card1 > card2:
temp = card1
card1 = card2
card2 = temp
return (card1, card2)
if __name__ == '__main__':
deck = Deck()
for _ in range(3):
c1, c2 = deal_bookends(deck)
print("We have {} and {}".format(c1, c2))
print(deck)
deck.return_card_and_shuffle(c1)
print(deck)
print(deck.contents[-4:])
deck.return_card_and_shuffle(c2)
print(deck)
print(deck.contents[-4:])
On running, I get the following error:
We have 8H and KH
50 cards: 9H, 8C, AC, 7C, 6H, 2S, 2D, 5C, 10H, 5H, JS, 5S, KD, JH, JC, QS, 2H, 3H, 3S, 3D, 4C, 4H, AD, KS, JD, QH, 10D, 6S, 5D, 8D, 3C, 6C, 7D, AS, 7H, AH, 9S, 10C, QC, QD, 7S, 2C, KC, 8S, 4D, 4S, 6D, 10S, 9D, 9C
51 cards: QS
[7D, 5C, 10H, QS]
52 cards: 10C
[KC, 3S, 9H, 10C]
We have 2C and QD
Traceback (most recent call last):
File "playing_cards.py", line 106, in <module>
print(deck)
File "playing_cards.py", line 88, in __str__
for card in self:
File "playing_cards.py", line 73, in __next__
item = self.contents[self.index]
IndexError: list index out of range
It seems the thing doesn't for the second run of the for loop when I push the card object back into the list. How do I solve this while keeping the pop,push functionality.
Edit: The self.index is at 50 after the first call to print(). When the card is added back to list, index remains at 50, whereas the deck length is now 51 cards. So in the second (and third) call to print the last card is printed instead of the entire deck. Then subsequently error is raised.
I think I have read the documentation wrong here. My question is should I reset the index at the StopIteration bit. Is that the correct way to do this, or is the index supposed to reset on its own?
Note: If you are trying to learn how iterators work by implementing your own, then the above advice holds. If you just want to make your Deck iterable, you can just do this in Deck:
def __iter__(self):
return self.contents # lists are already iterable
Even better, if you want your deck to behave like a list (iterating, indexing, slicing, removal), you can just extend list.
Learning how iterators work:
The problem you have here is you are conflating a collection with an iterator. A collection should hold a group of items. Your Deck is a collection. A collection is iterable, which means I can do for x in collection on it. When we do for x in collection, Python actually does for x in iter(collection), which turns the collection into an iterator.
You want your iterator and collection to be separate. If you collection was its own iterator, then you can only have one iterator over it at a time (itself). Also note that iterators should only be used once. By doing self.index = 0 in your __iter__, you are making your iterator (Deck) reusable.
Consider the following:
nums = [1, 2, 3]
for i in nums:
for j in nums:
print(i, j)
We expect this to return:
1 1
1 2
1 3
2 1
2 2
2 3
3 1
3 2
3 3
Note that each time the inner loop iterates over the whole collection. If nums was its own iterator, then we'd have some issues:
# Here internally nums sets the current index as 0
for i in nums:
# Here internally nums sets the current index as 0 again
for j in nums:
print(i, j)
# Once this inner loop finishes the current index is 4.
# But that is also the index for the outer loop, so the
# outer loop ends too
Unexpected output:
1 1
1 2
1 3
The solution is Deck.__iter__ should return a new object called DeckIterator, which keeps track of its own index. DeckIterator.__iter__ should return self (as required by the docs), but that is just a detail. By doing this you enable multiple iterations over the deck at once that work as expected.
So a minimal example of this would be:
class Deck:
# ... snip ...
def __iter__(self):
return DeckIterator(self.contents)
class DeckIterator:
def __init__(self, cards):
self.index = 0
self.cards = cards
def __iter__(self):
return self
def __next__(self):
if self.index >= len(self.cards):
# We've gotten the end of the deck/list
raise StopIteration
item = self.cards[self.index]
self.index += 1
return item
Also, if you don't believe me about this list as its own iterator, here's a list that exhibits this bad behavior:
class BadList(list):
def __iter__(self):
self._current_index = 0
return self
def __next__(self):
print(f'current index is {self._current_index}', end='')
if self._current_index >= len(self):
print(' which is the end, so ending iteration')
raise StopIteration
item = self[self._current_index]
print(f' so returning value {item}')
self._current_index += 1
return item
# Using letters instead of numbers so difference between indices
# and items is more clear
letters = BadList('abc')
for i in letters:
for j in letters:
print(i, j)
Output from it:
current index is 0 so returning value "a"
current index is 0 so returning value "a"
a a
current index is 1 so returning value "b"
a b
current index is 2 so returning value "c"
a c
current index is 3 which is the end, so ending iteration
current index is 3 which is the end, so ending iteration
Not sure how you got there, but you are beyond the length of your list. Suggest you compare for >= length of the list like:
def __next__(self):
if self.index >= len(self.contents):
raise StopIteration
.....
Make the following changes,
def __iter__(self):
self.index = 0
return self
So that each time __iter__ is called, index is reset.
The reason your'e getting this error is, once you iterate through the deck, at the end of the iteration, self.index == len(self.contents).
The next time you iterate, the self.index should be reset to 0.
I made the above change and it worked for me.
Your specific issue at the moment is caused by the check in your __next__ method not being general enough to detect all situations where you've iterated past the last value in self.contents. Since self.contents can change, you need to use a greater-than-or-equal test:
if self.index >= len(self.contents):
This will fix the current issue, but you'll still have other problems, since your Deck can only be iterated once. That's because you've implemented the iterator protocol, rather than the iterable protocol. These are easy to confuse, so don't feel bad if you don't understand the difference immediately.
An iterable is any object with an __iter__ method that returns an iterator. Some iterables return different iterators each time they're called, so you can iterate on them multiple times.
An iterator implements a __next__ method, which yields the next value or raises StopIteration. An iterator must also have an __iter__ method, which returns itself, which allows an iterator to be used wherever an iterable is expected, though it can only be iterated on once.
For your Deck, it probably makes sense to implement the iterable protocol, and return a separate iterator each time __iter__ is called. It's only rarely useful to implement your own iterator type, but if you want to test your knowledge of how the different protocols fit together, it can be interesting:
class Deck:
def __init__(self):
self.contents = [Card(suite, rank)
for suite in Card.get_suites()
for rank in Card.get_ranks()]
random.shuffle(self.contents)
# no index here
def __iter__(self):
return DeckIterator(self)
# other methods, but no __next__
class DeckIterator:
def __init__(self, deck):
self.deck = deck
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index > len(self.deck):
raise StopIteration
value = self.deck.contents[self.index]
self.index += 1
return value
A more practical approach is to have Deck.__iter__ borrow some convenient iterator type. For instance, you could do return iter(self.contents) and you'd get an iterator that works exactly like the custom version above. Another option is to make __iter__ a generator function, since generator objects are iterators. This can be convenient if you need to do just a little bit of processing on each item as you iterate over it.
I'm creating an interator like so:
some_list = [1,2,5,12,30,75,180,440]
iter(some_list)
I have a need to access the current value of an iterator again. Is there a current() method that allows me to stay on the same position?
You certainly can make a class which will allow you to do this:
from collections import deque
class RepeatableIter(object):
def __init__(self,iterable):
self.iter = iter(iterable)
self.deque = deque([])
def __iter__(self):
return self
#define `next` and `__next__` for forward/backward compatability
def next(self):
if self.deque:
return self.deque.popleft()
else:
return next(self.iter)
__next__ = next
def requeue(self,what):
self.deque.append(what)
x = RepeatableIter([1, 2, 3, 4, 5, 6])
count = 0
for i in x:
print i
if i == 4 and count == 0:
count += 1
x.requeue(i)
The question is really why would you want to?
You can use numpy.nditer to build your iterator, then you have many amazing options including the current value.
import numpy
rng = range(100)
itr = numpy.nditer([rng])
print itr.next() #0
print itr.next() #1
print itr.next() #2
print itr.value #2 : current iterator value
Adapting the third example from this answer:
class CurrentIterator():
_sentinal = object()
_current = _sentinal
#property
def current(self):
if self._current is self._sentinal:
raise ValueError('No current value')
return self._current
def __init__(self, iterable):
self.it = iter(iterable)
def __iter__(self):
return self
def __next__(self):
try:
self._current = current = next(self.it)
except StopIteration:
self._current = self._sentinal
raise
return current
next = __next__ # for python2.7 compatibility
Some interesting points:
use of _sentinal so an error can be raised if no current value exists
use of property so current looks like a simple attribute
use of __next__ and next = __next__ for Python 2&3 compatibility
Is there anyway to make a python list iterator to go backwards?
Basically i have this
class IterTest(object):
def __init__(self, data):
self.data = data
self.__iter = None
def all(self):
self.__iter = iter(self.data)
for each in self.__iter:
mtd = getattr(self, type(each).__name__)
mtd(each)
def str(self, item):
print item
next = self.__iter.next()
while isinstance(next, int):
print next
next = self.__iter.next()
def int(self, item):
print "Crap i skipped C"
if __name__ == '__main__':
test = IterTest(['a', 1, 2,3,'c', 17])
test.all()
Running this code results in the output:
a
1
2
3
Crap i skipped C
I know why it gives me the output, however is there a way i can step backwards in the str() method, by one step?
EDIT
Okay maybe to make this more clear. I don't want to do a full reverse, basically what i want to know if there is an easy way to do the equivalent of a bidirectional iterator in python?
No, in general you cannot make a Python iterator go backwards. However, if you only want to step back once, you can try something like this:
def str(self, item):
print item
prev, current = None, self.__iter.next()
while isinstance(current, int):
print current
prev, current = current, self.__iter.next()
You can then access the previous element any time in prev.
If you really need a bidirectional iterator, you can implement one yourself, but it's likely to introduce even more overhead than the solution above:
class bidirectional_iterator(object):
def __init__(self, collection):
self.collection = collection
self.index = 0
def next(self):
try:
result = self.collection[self.index]
self.index += 1
except IndexError:
raise StopIteration
return result
def prev(self):
self.index -= 1
if self.index < 0:
raise StopIteration
return self.collection[self.index]
def __iter__(self):
return self
Am I missing something or couldn't you use the technique described in the Iterator section in the Python tutorial?
>>> class reverse_iterator:
... def __init__(self, collection):
... self.data = collection
... self.index = len(self.data)
... def __iter__(self):
... return self
... def next(self):
... if self.index == 0:
... raise StopIteration
... self.index = self.index - 1
... return self.data[self.index]
...
>>> for each in reverse_iterator(['a', 1, 2, 3, 'c', 17]):
... print each
...
17
c
3
2
1
a
I know that this doesn't walk the iterator backwards, but I'm pretty sure that there is no way to do that in general. Instead, write an iterator that walks a discrete collection in reverse order.
Edit you can also use the reversed() function to get a reversed iterator for any collection so that you don't have to write your own:
>>> it = reversed(['a', 1, 2, 3, 'c', 17])
>>> type(it)
<type 'listreverseiterator'>
>>> for each in it:
... print each
...
17
c
3
2
1
a
An iterator is by definition an object with the next() method -- no mention of prev() whatsoever. Thus, you either have to cache your results so you can revisit them or reimplement your iterator so it returns results in the sequence you want them to be.
Based on your question, it sounds like you want something like this:
class buffered:
def __init__(self,it):
self.it = iter(it)
self.buf = []
def __iter__(self): return self
def __next__(self):
if self.buf:
return self.buf.pop()
return next(self.it)
def push(self,item): self.buf.append(item)
if __name__=="__main__":
b = buffered([0,1,2,3,4,5,6,7])
print(next(b)) # 0
print(next(b)) # 1
b.push(42)
print(next(b)) # 42
print(next(b)) # 2
You can enable an iterator to move backwards by following code.
class EnableBackwardIterator:
def __init__(self, iterator):
self.iterator = iterator
self.history = [None, ]
self.i = 0
def next(self):
self.i += 1
if self.i < len(self.history):
return self.history[self.i]
else:
elem = next(self.iterator)
self.history.append(elem)
return elem
def prev(self):
self.i -= 1
if self.i == 0:
raise StopIteration
else:
return self.history[self.i]
Usage:
>>> prev = lambda obj: obj.prev() # A syntactic sugar.
>>>
>>> a = EnableBackwardIterator(iter([1,2,3,4,5,6]))
>>>
>>> next(a)
1
>>> next(a)
2
>>> a.next() # The same as `next(a)`.
3
>>> prev(a)
2
>>> a.prev() # The same as `prev(a)`.
1
>>> next(a)
2
>>> next(a)
3
>>> next(a)
4
>>> next(a)
5
>>> next(a)
6
>>> prev(a)
5
>>> prev(a)
4
>>> next(a)
5
>>> next(a)
6
>>> next(a)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
You can wrap your iterator in an iterator helper to enable it to go backward. It will store the iterated values in a collection and reuse them when going backwards.
class MemoryIterator:
def __init__(self, iterator : Iterator):
self._iterator : Iterator = iterator
self._array = []
self._isComplete = False
self._pointer = 0
def __next__(self):
if self._isComplete or self._pointer < len(self._array):
if self._isComplete and self._pointer >= len(self._array):
raise StopIteration
value = self._array[self._pointer]
self._pointer = self._pointer + 1
return value
try:
value = next(self._iterator)
self._pointer = self._pointer + 1
self._array.append(value)
return value
except StopIteration:
self._isComplete = True
def prev(self):
if self._pointer - 2 < 0:
raise StopIteration
self._pointer = self._pointer - 1
return self._array[self._pointer - 1]
The usage can be similar to this one:
my_iter = iter(my_iterable_source)
memory_iterator = MemoryIterator(my_iter)
try:
if forward:
print(next(memory_iterator))
else:
print(memory_iterator.prev())
except StopIteration:
pass
I came here looking for a bi-directional iterator. Not sure if this is what the OP was looking for but it is one way to make a bi-directional iterator—by giving it an attribute to indicate which direction to go in next:
class BidirectionalCounter:
"""An iterator that can count in two directions (up
and down).
"""
def __init__(self, start):
self.forward = True
# Code to initialize the sequence
self.x = start
def __iter__(self):
return self
def __next__(self):
if self.forward:
return self.next()
else:
return self.prev()
def reverse(self):
self.forward = not self.forward
def next(self):
"""Compute and return next value in sequence.
"""
# Code to go forward
self.x += 1
return self.x
def prev(self):
"""Compute and return previous value in sequence.
"""
# Code to go backward
self.x -= 1
return self.x
Demo:
my_counter = BidirectionalCounter(10)
print(next(my_counter))
print(next(my_counter))
my_counter.reverse()
print(next(my_counter))
print(next(my_counter))
Output:
11
12
11
10
i think thi will help you to solve your problem
class TestIterator():
def __init__(self):`
self.data = ["MyData", "is", "here","done"]
self.index = -1
#self.index=len(self.data)-1
def __iter__(self):
return self
def next(self):
self.index += 1
if self.index >= len(self.data):
raise StopIteration
return self.data[self.index]
def __reversed__(self):
self.index = -1
if self.index >= len(self.data):
raise StopIteration
return self.data[self.index]
r = TestIterator()
itr=iter(r)
print (next(itr))
print (reversed(itr))
ls = [' a', 5, ' d', 7, 'bc',9, ' c', 17, '43', 55, 'ab',22, 'ac']
direct = -1
l = ls[::direct]
for el in l:
print el
Where direct is -1 for reverse or 1 for ordinary.
Python you can use a list and indexing to simulate an iterator:
a = [1,2,3]
current = 1
def get_next(a):
current = a[a.index(current)+1%len(a)]
return current
def get_last(a):
current = a[a.index(current)-1]
return current # a[-1] >>> 3 (negative safe)
if your list contains duplicates then you would have to track your index separately:
a =[1,2,3]
index = 0
def get_next(a):
index = index+1 % len(a)
current = a[index]
return current
def get_last(a):
index = index-1 % len(a)
current = a[index-1]
return current # a[-1] >>> 3 (negative safe)
An iterator that visits the elements of a list in reverse order:
class ReverseIterator:
def __init__(self,ls):
self.ls=ls
self.index=len(ls)-1
def __iter__(self):
return self
def __next__(self):
if self.index<0:
raise StopIteration
result = self.ls[self.index]
self.index -= 1
return result
I edited the python code from dilshad (thank you) and used the following Python 3 based code to step between list item's back and forth or let say bidirectional:
# bidirectional class
class bidirectional_iterator:
def __init__(self):
self.data = ["MyData", "is", "here", "done"]
self.index = -1
def __iter__(self):
return self
def __next__(self):
self.index += 1
if self.index >= len(self.data):
raise StopIteration
return self.data[self.index]
def __reversed__(self):
self.index -= 1
if self.index == -1:
raise StopIteration
return self.data[self.index]
Example:
>>> r = bidirectional_iterator()
>>> itr=iter(r)
>>> print (next(itr))
MyData
>>> print (next(itr))
is
>>> print (next(itr))
here
>>> print (reversed(itr))
is
>>> print (reversed(itr))
MyData
>>> print (next(itr))
is
This is a common situation when we need to make an iterator go back one step. Because we should get the item and then check if we should break the loop. When breaking the loop, the last item may be requied in later usage.
Except of implementing an iteration class, here is a handy way make use of builtin itertools.chain :
from itertools import chain
>>> iterator = iter(range(10))
>>> for i in iterator:
... if i <= 5:
... print(i)
... else:
... iterator = chain([i], iterator) # push last value back
... break
...
0
1
2
3
4
5
>>> for i in iterator:
... print(i)
...
6
7
8
9
please see this function made by Morten Piibeleht. It yields a (previous, current, next) tuple for every element of an iterable.
https://gist.github.com/mortenpi/9604377