Confusions about python Iterators










0















The more I practice Iterators, the more I get confused. I feel pretty confident in Objects and Classes (Only thing we have learned, Not learned inheritance) but Iterators and generators mess my head around. Any help is highly appreciated.



I have a some questions:



1) In the code below:



class main():
def __init__(self):
self.items=[1,2,3,4,5,6,7]
self.index= 0

def __iter__(self):
return self

def __next__(self):
self.index+=1

return self.items[self.index]

a = main()

for i in a:
print(i)


  1. We have two self here. One is in the init which is referring to the object 'a' and another one is returned by self. what is the real data type of self? is it of type main() or it is iterator?

  2. Similar to the above question - when we give do next(self), what self are we giving to next (iterator or of type(a)) ?

  3. If self after returned by __iter__ (also being used by next), is of the type iterator , how can be access self.index?

2) In the code below I am trying to make iterate over specific thing such as keys or values or items in dictionary class.
It is throwing an error ''iterator' object has no attribute 'index'.
Why cant self.index access the instance variable index of dictionary class?



class Pair():
def __init__(self, key ,value):
self.key = key
self.value = value

class Dictionary():
def __init__(self):
self.items =
self.index = -1 ################## INDEX DEFINED HERE

def __setitem__(self, key, value):
for i in self.items:
if i.key == key:
i.value = value
return
self.items.append(Pair(key,value))

def __keys__(self):
return iterator(self, 'keys')

def __values__(self):
return iterator(self, 'values')

def __items__(self):
return iterator(self , 'items')

class iterator():
def __init__(self, object, typo):
self.typo = typo

def __iter__(self):
return self

def __next__(self):
if self.typo == 'keys':
self.index +=1 #################### ERROR
if self.index >= len(self.items):
raise StopIteration
return self.items[self.index].keys

` # Similarly for keys and items as well`

collins = Dictionary()

collins['google'] = 'pixel'
collins['htc'] = 'M8'
collins['samsung'] = 'S9'


for i in collins.__keys__():
print(i)









share|improve this question

















  • 1





    It is of type main. "Iterator" is not a type, but a certain behaviour of some objects. Regarding your second part: you don't assign object to anything, e.g. self.object. Then you could do self.object.index...

    – L3viathan
    Nov 13 '18 at 12:34
















0















The more I practice Iterators, the more I get confused. I feel pretty confident in Objects and Classes (Only thing we have learned, Not learned inheritance) but Iterators and generators mess my head around. Any help is highly appreciated.



I have a some questions:



1) In the code below:



class main():
def __init__(self):
self.items=[1,2,3,4,5,6,7]
self.index= 0

def __iter__(self):
return self

def __next__(self):
self.index+=1

return self.items[self.index]

a = main()

for i in a:
print(i)


  1. We have two self here. One is in the init which is referring to the object 'a' and another one is returned by self. what is the real data type of self? is it of type main() or it is iterator?

  2. Similar to the above question - when we give do next(self), what self are we giving to next (iterator or of type(a)) ?

  3. If self after returned by __iter__ (also being used by next), is of the type iterator , how can be access self.index?

2) In the code below I am trying to make iterate over specific thing such as keys or values or items in dictionary class.
It is throwing an error ''iterator' object has no attribute 'index'.
Why cant self.index access the instance variable index of dictionary class?



class Pair():
def __init__(self, key ,value):
self.key = key
self.value = value

class Dictionary():
def __init__(self):
self.items =
self.index = -1 ################## INDEX DEFINED HERE

def __setitem__(self, key, value):
for i in self.items:
if i.key == key:
i.value = value
return
self.items.append(Pair(key,value))

def __keys__(self):
return iterator(self, 'keys')

def __values__(self):
return iterator(self, 'values')

def __items__(self):
return iterator(self , 'items')

class iterator():
def __init__(self, object, typo):
self.typo = typo

def __iter__(self):
return self

def __next__(self):
if self.typo == 'keys':
self.index +=1 #################### ERROR
if self.index >= len(self.items):
raise StopIteration
return self.items[self.index].keys

` # Similarly for keys and items as well`

collins = Dictionary()

collins['google'] = 'pixel'
collins['htc'] = 'M8'
collins['samsung'] = 'S9'


for i in collins.__keys__():
print(i)









share|improve this question

















  • 1





    It is of type main. "Iterator" is not a type, but a certain behaviour of some objects. Regarding your second part: you don't assign object to anything, e.g. self.object. Then you could do self.object.index...

    – L3viathan
    Nov 13 '18 at 12:34














0












0








0








The more I practice Iterators, the more I get confused. I feel pretty confident in Objects and Classes (Only thing we have learned, Not learned inheritance) but Iterators and generators mess my head around. Any help is highly appreciated.



I have a some questions:



1) In the code below:



class main():
def __init__(self):
self.items=[1,2,3,4,5,6,7]
self.index= 0

def __iter__(self):
return self

def __next__(self):
self.index+=1

return self.items[self.index]

a = main()

for i in a:
print(i)


  1. We have two self here. One is in the init which is referring to the object 'a' and another one is returned by self. what is the real data type of self? is it of type main() or it is iterator?

  2. Similar to the above question - when we give do next(self), what self are we giving to next (iterator or of type(a)) ?

  3. If self after returned by __iter__ (also being used by next), is of the type iterator , how can be access self.index?

2) In the code below I am trying to make iterate over specific thing such as keys or values or items in dictionary class.
It is throwing an error ''iterator' object has no attribute 'index'.
Why cant self.index access the instance variable index of dictionary class?



class Pair():
def __init__(self, key ,value):
self.key = key
self.value = value

class Dictionary():
def __init__(self):
self.items =
self.index = -1 ################## INDEX DEFINED HERE

def __setitem__(self, key, value):
for i in self.items:
if i.key == key:
i.value = value
return
self.items.append(Pair(key,value))

def __keys__(self):
return iterator(self, 'keys')

def __values__(self):
return iterator(self, 'values')

def __items__(self):
return iterator(self , 'items')

class iterator():
def __init__(self, object, typo):
self.typo = typo

def __iter__(self):
return self

def __next__(self):
if self.typo == 'keys':
self.index +=1 #################### ERROR
if self.index >= len(self.items):
raise StopIteration
return self.items[self.index].keys

` # Similarly for keys and items as well`

collins = Dictionary()

collins['google'] = 'pixel'
collins['htc'] = 'M8'
collins['samsung'] = 'S9'


for i in collins.__keys__():
print(i)









share|improve this question














The more I practice Iterators, the more I get confused. I feel pretty confident in Objects and Classes (Only thing we have learned, Not learned inheritance) but Iterators and generators mess my head around. Any help is highly appreciated.



I have a some questions:



1) In the code below:



class main():
def __init__(self):
self.items=[1,2,3,4,5,6,7]
self.index= 0

def __iter__(self):
return self

def __next__(self):
self.index+=1

return self.items[self.index]

a = main()

for i in a:
print(i)


  1. We have two self here. One is in the init which is referring to the object 'a' and another one is returned by self. what is the real data type of self? is it of type main() or it is iterator?

  2. Similar to the above question - when we give do next(self), what self are we giving to next (iterator or of type(a)) ?

  3. If self after returned by __iter__ (also being used by next), is of the type iterator , how can be access self.index?

2) In the code below I am trying to make iterate over specific thing such as keys or values or items in dictionary class.
It is throwing an error ''iterator' object has no attribute 'index'.
Why cant self.index access the instance variable index of dictionary class?



class Pair():
def __init__(self, key ,value):
self.key = key
self.value = value

class Dictionary():
def __init__(self):
self.items =
self.index = -1 ################## INDEX DEFINED HERE

def __setitem__(self, key, value):
for i in self.items:
if i.key == key:
i.value = value
return
self.items.append(Pair(key,value))

def __keys__(self):
return iterator(self, 'keys')

def __values__(self):
return iterator(self, 'values')

def __items__(self):
return iterator(self , 'items')

class iterator():
def __init__(self, object, typo):
self.typo = typo

def __iter__(self):
return self

def __next__(self):
if self.typo == 'keys':
self.index +=1 #################### ERROR
if self.index >= len(self.items):
raise StopIteration
return self.items[self.index].keys

` # Similarly for keys and items as well`

collins = Dictionary()

collins['google'] = 'pixel'
collins['htc'] = 'M8'
collins['samsung'] = 'S9'


for i in collins.__keys__():
print(i)






python python-3.x python-2.7 iterator iterable






share|improve this question













share|improve this question











share|improve this question




share|improve this question










asked Nov 13 '18 at 11:29









lynxxlynxx

235




235







  • 1





    It is of type main. "Iterator" is not a type, but a certain behaviour of some objects. Regarding your second part: you don't assign object to anything, e.g. self.object. Then you could do self.object.index...

    – L3viathan
    Nov 13 '18 at 12:34













  • 1





    It is of type main. "Iterator" is not a type, but a certain behaviour of some objects. Regarding your second part: you don't assign object to anything, e.g. self.object. Then you could do self.object.index...

    – L3viathan
    Nov 13 '18 at 12:34








1




1





It is of type main. "Iterator" is not a type, but a certain behaviour of some objects. Regarding your second part: you don't assign object to anything, e.g. self.object. Then you could do self.object.index...

– L3viathan
Nov 13 '18 at 12:34






It is of type main. "Iterator" is not a type, but a certain behaviour of some objects. Regarding your second part: you don't assign object to anything, e.g. self.object. Then you could do self.object.index...

– L3viathan
Nov 13 '18 at 12:34













3 Answers
3






active

oldest

votes


















1














I have rewritten your code a bit with lots of comments to try and explain what is happening in example (1).



class MainClass():
def __init__(self):
# The value 'self' always refers to the object we are currently working
# on. In this case, we are instantiating a new object of class
# MainClass, so self refers to that new object.
# self.items is an instance variable called items within the object
# referred to as self.
self.items = [1, 2, 3, 4, 5, 6, 7]
# We do not want to declare self.index here. This is a slightly subtle
# point. If we declare index here, then it will only be set when we first
# create an object of class MainClass. We actually want self.index to be
# set to zero each time we iterate over the object, so we should set it
# to zero in the __iter__(self) method.
# self.index = 0

def __iter__(self):
# This is an instance method, which operates on the current instance of
# MainClass (an object of class MainClass). This method is called when
# we start iteration on an object, so as stated above, we'll set
# self.index to zero.
self.index = 0
return self

def __next__(self):
# This is also an instance method, which operates on the current
# instance of MainClass.
if self.index < len(self.items):
self.index += 1
return self.items[self.index - 1]
else:
# This is how we know when to stop iterating.
raise StopIteration()


a = MainClass()

# a is now an object of class MainClass
# Because we have implemented __iter__ and __next__ methods in MainClass,
# objects of class MainClass are iterable, so a is also iterable.

# When we say "for i in a" this is like shorthand for saying "a.__iter__()"
# and then "i = a.__next__()" until we raise
# a StopIterationException

# Here we are iterating over the result of a.__iter__() until a.__next__()
# raises a StopIterationException
for i in a:
# Here we are printing the value returned by a.__next__()
print(i)


I think it might help you to review this before you move on to (2) and double-check what you know about objects and classes. The first problem we can see in (2) is that you pass an object to your iterator class, but don't store it anywhere so you have no way to access it later. But you may find you have other ways you want to change it when you more fully understand all you have asked about in (1).






share|improve this answer

























  • Hi @rbricheno , thank you for your reply, it really helped me understand more in depth. ** I have finally written the working code for Part 2** . If you have time, would be great if you have look over it. I have posted it as an answer.

    – lynxx
    Nov 13 '18 at 15:12


















1














This answers only your first question, and might help you with question 2.



Citing from 'Fluent Python' (p. 420):




[...] Objects implementing an __iter__ method returning an iterator are iterable. [...]




That means, you could (in theory) do something like this:



class Main:
def __init__(self):
self.items = list(range(1, 8))
self.length = len(self.items)

def __iter__(self):
return MainIterator(self)


Now, but how does the MainIterator class look like? The iterator just needs a __next__ dunder method to determine the next value it returns. An implementation could look like this:



class MainIterator:
def __init__(self, iterable):
self.iterable = iterable
self.index = 0

def __next__(self):
if self.index >= self.iterable.length:
raise StopIteration

self.index += 1
return self.iterable.items[self.index - 1]


What I am basically doing is creating a reference to the calling iterable and saving it in self.iterable. Now every time the __next__ dunder method is called, it returns an element of the array, until the iterator is exhausted. This is indicated by raising StopIteration.



You do not see such an implementation very often, as these two classes are often merged into a single class. I just wanted to demonstrate that it is possible to separate the two. The result is what @rbricheno already posted:



class Main:
def __init__(self):
self.items = list(range(1, 8))
self.length = len(self.items)

def __iter__(self):
self.index = 0
return self

def __next__(self):
if self.index >= self.length:
raise StopIteration

self.index += 1
return self.items[self.index - 1]


The difference is that __init__ returns the instance itself, as the class itself is now iterable and iterator (remember: an iterator has the __next__ dunder method, and an iterable has a __iter__ dunder method that returns an iterator).



The last interesting bit is, when these dunder methods are called. Actually, when using the for in syntax, it is syntactic sugar for:



a = Main()

## recreating the for in loop

itr = a.__iter__()

while True:
try:
print(itr.__next__())
except StopIteration:
break


You initialize the iterator first, and __next__ returns a value until the iterator is exhausted.



EDIT:



You should really read my post again. It is NOT good practice to separate the iterator. It's just to demonstrate how they work internally. Also, please do not define your own dunder methods. That will break your code at some time. I have corrected your dict class below, but I iterate over the pair, not its components.



class Pair:

def __init__(self, key, value):
self.key = key
self.value = value

## you need this to display your class in a meaningful way
def __repr__(self):
return f'__class__.__name__(self.key, self.value)'

class Dictionary:

def __init__(self):
self.items =
self.length = len(self.items)

def add(self, objects):
self.items.append(objects)
self.length += 1

def __iter__(self):
self.index = 0
return self

def __next__(self):
if self.index >= self.length:
raise StopIteration

self.index += 1
return self.items[self.index - 1]

a = Dictionary()

a.add(Pair('up', 'above'))
a.add(Pair('down', 'below'))

for i in a:
print(i.key)
print(i.value)


Output on my machine:



up
above
down
below





share|improve this answer

























  • Hi @DocDriven, thank you for your reply, it really helped me understand more in depth. ** I have finally written the working code for Part 2** . If you have time, would be great if you have look over it. I have posted it as an answer.

    – lynxx
    Nov 13 '18 at 15:12


















0














Thats what I came up with:



class Pair():
def __init__(self, key, value):
self.key = key
self.value = value


class dictionary():
def __init__(self):
self.items =

def add(self, objects):
self.items.append(objects)

def __keys__(self):
return iterator(self, 'keys')

def __values__(self):
return iterator(self, 'values')

class iterator():
def __init__(self, to_be_iterated , over_what):
self.to_be_iterated = to_be_iterated
self.over_what = over_what


def __iter__(self):
self.index = -1
return self

def __next__(self):
self.index += 1
if self.over_what == 'keys':
try:
return self.to_be_iterated.items[self.index].key
except Exception:
raise StopIteration

elif self.over_what == 'values':
try:
return self.to_be_iterated.items[self.index].value
except Exception:
raise StopIteration


collins = dictionary()

collins.add(Pair('up', 'above'))
collins.add(Pair('down', 'below'))

for i in collins.__keys__():
print(i)

for i in collins.__values__():
print(i)





share|improve this answer


















  • 1





    I am not sure if you got the idea completely, however, I edited my answer with a possible solution to your problem.

    – DocDriven
    Nov 13 '18 at 15:40











  • I got it! thanks heaps you both.

    – lynxx
    Nov 13 '18 at 16:23










Your Answer






StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);













draft saved

draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53280063%2fconfusions-about-python-iterators%23new-answer', 'question_page');

);

Post as a guest















Required, but never shown

























3 Answers
3






active

oldest

votes








3 Answers
3






active

oldest

votes









active

oldest

votes






active

oldest

votes









1














I have rewritten your code a bit with lots of comments to try and explain what is happening in example (1).



class MainClass():
def __init__(self):
# The value 'self' always refers to the object we are currently working
# on. In this case, we are instantiating a new object of class
# MainClass, so self refers to that new object.
# self.items is an instance variable called items within the object
# referred to as self.
self.items = [1, 2, 3, 4, 5, 6, 7]
# We do not want to declare self.index here. This is a slightly subtle
# point. If we declare index here, then it will only be set when we first
# create an object of class MainClass. We actually want self.index to be
# set to zero each time we iterate over the object, so we should set it
# to zero in the __iter__(self) method.
# self.index = 0

def __iter__(self):
# This is an instance method, which operates on the current instance of
# MainClass (an object of class MainClass). This method is called when
# we start iteration on an object, so as stated above, we'll set
# self.index to zero.
self.index = 0
return self

def __next__(self):
# This is also an instance method, which operates on the current
# instance of MainClass.
if self.index < len(self.items):
self.index += 1
return self.items[self.index - 1]
else:
# This is how we know when to stop iterating.
raise StopIteration()


a = MainClass()

# a is now an object of class MainClass
# Because we have implemented __iter__ and __next__ methods in MainClass,
# objects of class MainClass are iterable, so a is also iterable.

# When we say "for i in a" this is like shorthand for saying "a.__iter__()"
# and then "i = a.__next__()" until we raise
# a StopIterationException

# Here we are iterating over the result of a.__iter__() until a.__next__()
# raises a StopIterationException
for i in a:
# Here we are printing the value returned by a.__next__()
print(i)


I think it might help you to review this before you move on to (2) and double-check what you know about objects and classes. The first problem we can see in (2) is that you pass an object to your iterator class, but don't store it anywhere so you have no way to access it later. But you may find you have other ways you want to change it when you more fully understand all you have asked about in (1).






share|improve this answer

























  • Hi @rbricheno , thank you for your reply, it really helped me understand more in depth. ** I have finally written the working code for Part 2** . If you have time, would be great if you have look over it. I have posted it as an answer.

    – lynxx
    Nov 13 '18 at 15:12















1














I have rewritten your code a bit with lots of comments to try and explain what is happening in example (1).



class MainClass():
def __init__(self):
# The value 'self' always refers to the object we are currently working
# on. In this case, we are instantiating a new object of class
# MainClass, so self refers to that new object.
# self.items is an instance variable called items within the object
# referred to as self.
self.items = [1, 2, 3, 4, 5, 6, 7]
# We do not want to declare self.index here. This is a slightly subtle
# point. If we declare index here, then it will only be set when we first
# create an object of class MainClass. We actually want self.index to be
# set to zero each time we iterate over the object, so we should set it
# to zero in the __iter__(self) method.
# self.index = 0

def __iter__(self):
# This is an instance method, which operates on the current instance of
# MainClass (an object of class MainClass). This method is called when
# we start iteration on an object, so as stated above, we'll set
# self.index to zero.
self.index = 0
return self

def __next__(self):
# This is also an instance method, which operates on the current
# instance of MainClass.
if self.index < len(self.items):
self.index += 1
return self.items[self.index - 1]
else:
# This is how we know when to stop iterating.
raise StopIteration()


a = MainClass()

# a is now an object of class MainClass
# Because we have implemented __iter__ and __next__ methods in MainClass,
# objects of class MainClass are iterable, so a is also iterable.

# When we say "for i in a" this is like shorthand for saying "a.__iter__()"
# and then "i = a.__next__()" until we raise
# a StopIterationException

# Here we are iterating over the result of a.__iter__() until a.__next__()
# raises a StopIterationException
for i in a:
# Here we are printing the value returned by a.__next__()
print(i)


I think it might help you to review this before you move on to (2) and double-check what you know about objects and classes. The first problem we can see in (2) is that you pass an object to your iterator class, but don't store it anywhere so you have no way to access it later. But you may find you have other ways you want to change it when you more fully understand all you have asked about in (1).






share|improve this answer

























  • Hi @rbricheno , thank you for your reply, it really helped me understand more in depth. ** I have finally written the working code for Part 2** . If you have time, would be great if you have look over it. I have posted it as an answer.

    – lynxx
    Nov 13 '18 at 15:12













1












1








1







I have rewritten your code a bit with lots of comments to try and explain what is happening in example (1).



class MainClass():
def __init__(self):
# The value 'self' always refers to the object we are currently working
# on. In this case, we are instantiating a new object of class
# MainClass, so self refers to that new object.
# self.items is an instance variable called items within the object
# referred to as self.
self.items = [1, 2, 3, 4, 5, 6, 7]
# We do not want to declare self.index here. This is a slightly subtle
# point. If we declare index here, then it will only be set when we first
# create an object of class MainClass. We actually want self.index to be
# set to zero each time we iterate over the object, so we should set it
# to zero in the __iter__(self) method.
# self.index = 0

def __iter__(self):
# This is an instance method, which operates on the current instance of
# MainClass (an object of class MainClass). This method is called when
# we start iteration on an object, so as stated above, we'll set
# self.index to zero.
self.index = 0
return self

def __next__(self):
# This is also an instance method, which operates on the current
# instance of MainClass.
if self.index < len(self.items):
self.index += 1
return self.items[self.index - 1]
else:
# This is how we know when to stop iterating.
raise StopIteration()


a = MainClass()

# a is now an object of class MainClass
# Because we have implemented __iter__ and __next__ methods in MainClass,
# objects of class MainClass are iterable, so a is also iterable.

# When we say "for i in a" this is like shorthand for saying "a.__iter__()"
# and then "i = a.__next__()" until we raise
# a StopIterationException

# Here we are iterating over the result of a.__iter__() until a.__next__()
# raises a StopIterationException
for i in a:
# Here we are printing the value returned by a.__next__()
print(i)


I think it might help you to review this before you move on to (2) and double-check what you know about objects and classes. The first problem we can see in (2) is that you pass an object to your iterator class, but don't store it anywhere so you have no way to access it later. But you may find you have other ways you want to change it when you more fully understand all you have asked about in (1).






share|improve this answer















I have rewritten your code a bit with lots of comments to try and explain what is happening in example (1).



class MainClass():
def __init__(self):
# The value 'self' always refers to the object we are currently working
# on. In this case, we are instantiating a new object of class
# MainClass, so self refers to that new object.
# self.items is an instance variable called items within the object
# referred to as self.
self.items = [1, 2, 3, 4, 5, 6, 7]
# We do not want to declare self.index here. This is a slightly subtle
# point. If we declare index here, then it will only be set when we first
# create an object of class MainClass. We actually want self.index to be
# set to zero each time we iterate over the object, so we should set it
# to zero in the __iter__(self) method.
# self.index = 0

def __iter__(self):
# This is an instance method, which operates on the current instance of
# MainClass (an object of class MainClass). This method is called when
# we start iteration on an object, so as stated above, we'll set
# self.index to zero.
self.index = 0
return self

def __next__(self):
# This is also an instance method, which operates on the current
# instance of MainClass.
if self.index < len(self.items):
self.index += 1
return self.items[self.index - 1]
else:
# This is how we know when to stop iterating.
raise StopIteration()


a = MainClass()

# a is now an object of class MainClass
# Because we have implemented __iter__ and __next__ methods in MainClass,
# objects of class MainClass are iterable, so a is also iterable.

# When we say "for i in a" this is like shorthand for saying "a.__iter__()"
# and then "i = a.__next__()" until we raise
# a StopIterationException

# Here we are iterating over the result of a.__iter__() until a.__next__()
# raises a StopIterationException
for i in a:
# Here we are printing the value returned by a.__next__()
print(i)


I think it might help you to review this before you move on to (2) and double-check what you know about objects and classes. The first problem we can see in (2) is that you pass an object to your iterator class, but don't store it anywhere so you have no way to access it later. But you may find you have other ways you want to change it when you more fully understand all you have asked about in (1).







share|improve this answer














share|improve this answer



share|improve this answer








edited Nov 13 '18 at 12:52

























answered Nov 13 '18 at 12:39









Rob BrichenoRob Bricheno

2,375318




2,375318












  • Hi @rbricheno , thank you for your reply, it really helped me understand more in depth. ** I have finally written the working code for Part 2** . If you have time, would be great if you have look over it. I have posted it as an answer.

    – lynxx
    Nov 13 '18 at 15:12

















  • Hi @rbricheno , thank you for your reply, it really helped me understand more in depth. ** I have finally written the working code for Part 2** . If you have time, would be great if you have look over it. I have posted it as an answer.

    – lynxx
    Nov 13 '18 at 15:12
















Hi @rbricheno , thank you for your reply, it really helped me understand more in depth. ** I have finally written the working code for Part 2** . If you have time, would be great if you have look over it. I have posted it as an answer.

– lynxx
Nov 13 '18 at 15:12





Hi @rbricheno , thank you for your reply, it really helped me understand more in depth. ** I have finally written the working code for Part 2** . If you have time, would be great if you have look over it. I have posted it as an answer.

– lynxx
Nov 13 '18 at 15:12













1














This answers only your first question, and might help you with question 2.



Citing from 'Fluent Python' (p. 420):




[...] Objects implementing an __iter__ method returning an iterator are iterable. [...]




That means, you could (in theory) do something like this:



class Main:
def __init__(self):
self.items = list(range(1, 8))
self.length = len(self.items)

def __iter__(self):
return MainIterator(self)


Now, but how does the MainIterator class look like? The iterator just needs a __next__ dunder method to determine the next value it returns. An implementation could look like this:



class MainIterator:
def __init__(self, iterable):
self.iterable = iterable
self.index = 0

def __next__(self):
if self.index >= self.iterable.length:
raise StopIteration

self.index += 1
return self.iterable.items[self.index - 1]


What I am basically doing is creating a reference to the calling iterable and saving it in self.iterable. Now every time the __next__ dunder method is called, it returns an element of the array, until the iterator is exhausted. This is indicated by raising StopIteration.



You do not see such an implementation very often, as these two classes are often merged into a single class. I just wanted to demonstrate that it is possible to separate the two. The result is what @rbricheno already posted:



class Main:
def __init__(self):
self.items = list(range(1, 8))
self.length = len(self.items)

def __iter__(self):
self.index = 0
return self

def __next__(self):
if self.index >= self.length:
raise StopIteration

self.index += 1
return self.items[self.index - 1]


The difference is that __init__ returns the instance itself, as the class itself is now iterable and iterator (remember: an iterator has the __next__ dunder method, and an iterable has a __iter__ dunder method that returns an iterator).



The last interesting bit is, when these dunder methods are called. Actually, when using the for in syntax, it is syntactic sugar for:



a = Main()

## recreating the for in loop

itr = a.__iter__()

while True:
try:
print(itr.__next__())
except StopIteration:
break


You initialize the iterator first, and __next__ returns a value until the iterator is exhausted.



EDIT:



You should really read my post again. It is NOT good practice to separate the iterator. It's just to demonstrate how they work internally. Also, please do not define your own dunder methods. That will break your code at some time. I have corrected your dict class below, but I iterate over the pair, not its components.



class Pair:

def __init__(self, key, value):
self.key = key
self.value = value

## you need this to display your class in a meaningful way
def __repr__(self):
return f'__class__.__name__(self.key, self.value)'

class Dictionary:

def __init__(self):
self.items =
self.length = len(self.items)

def add(self, objects):
self.items.append(objects)
self.length += 1

def __iter__(self):
self.index = 0
return self

def __next__(self):
if self.index >= self.length:
raise StopIteration

self.index += 1
return self.items[self.index - 1]

a = Dictionary()

a.add(Pair('up', 'above'))
a.add(Pair('down', 'below'))

for i in a:
print(i.key)
print(i.value)


Output on my machine:



up
above
down
below





share|improve this answer

























  • Hi @DocDriven, thank you for your reply, it really helped me understand more in depth. ** I have finally written the working code for Part 2** . If you have time, would be great if you have look over it. I have posted it as an answer.

    – lynxx
    Nov 13 '18 at 15:12















1














This answers only your first question, and might help you with question 2.



Citing from 'Fluent Python' (p. 420):




[...] Objects implementing an __iter__ method returning an iterator are iterable. [...]




That means, you could (in theory) do something like this:



class Main:
def __init__(self):
self.items = list(range(1, 8))
self.length = len(self.items)

def __iter__(self):
return MainIterator(self)


Now, but how does the MainIterator class look like? The iterator just needs a __next__ dunder method to determine the next value it returns. An implementation could look like this:



class MainIterator:
def __init__(self, iterable):
self.iterable = iterable
self.index = 0

def __next__(self):
if self.index >= self.iterable.length:
raise StopIteration

self.index += 1
return self.iterable.items[self.index - 1]


What I am basically doing is creating a reference to the calling iterable and saving it in self.iterable. Now every time the __next__ dunder method is called, it returns an element of the array, until the iterator is exhausted. This is indicated by raising StopIteration.



You do not see such an implementation very often, as these two classes are often merged into a single class. I just wanted to demonstrate that it is possible to separate the two. The result is what @rbricheno already posted:



class Main:
def __init__(self):
self.items = list(range(1, 8))
self.length = len(self.items)

def __iter__(self):
self.index = 0
return self

def __next__(self):
if self.index >= self.length:
raise StopIteration

self.index += 1
return self.items[self.index - 1]


The difference is that __init__ returns the instance itself, as the class itself is now iterable and iterator (remember: an iterator has the __next__ dunder method, and an iterable has a __iter__ dunder method that returns an iterator).



The last interesting bit is, when these dunder methods are called. Actually, when using the for in syntax, it is syntactic sugar for:



a = Main()

## recreating the for in loop

itr = a.__iter__()

while True:
try:
print(itr.__next__())
except StopIteration:
break


You initialize the iterator first, and __next__ returns a value until the iterator is exhausted.



EDIT:



You should really read my post again. It is NOT good practice to separate the iterator. It's just to demonstrate how they work internally. Also, please do not define your own dunder methods. That will break your code at some time. I have corrected your dict class below, but I iterate over the pair, not its components.



class Pair:

def __init__(self, key, value):
self.key = key
self.value = value

## you need this to display your class in a meaningful way
def __repr__(self):
return f'__class__.__name__(self.key, self.value)'

class Dictionary:

def __init__(self):
self.items =
self.length = len(self.items)

def add(self, objects):
self.items.append(objects)
self.length += 1

def __iter__(self):
self.index = 0
return self

def __next__(self):
if self.index >= self.length:
raise StopIteration

self.index += 1
return self.items[self.index - 1]

a = Dictionary()

a.add(Pair('up', 'above'))
a.add(Pair('down', 'below'))

for i in a:
print(i.key)
print(i.value)


Output on my machine:



up
above
down
below





share|improve this answer

























  • Hi @DocDriven, thank you for your reply, it really helped me understand more in depth. ** I have finally written the working code for Part 2** . If you have time, would be great if you have look over it. I have posted it as an answer.

    – lynxx
    Nov 13 '18 at 15:12













1












1








1







This answers only your first question, and might help you with question 2.



Citing from 'Fluent Python' (p. 420):




[...] Objects implementing an __iter__ method returning an iterator are iterable. [...]




That means, you could (in theory) do something like this:



class Main:
def __init__(self):
self.items = list(range(1, 8))
self.length = len(self.items)

def __iter__(self):
return MainIterator(self)


Now, but how does the MainIterator class look like? The iterator just needs a __next__ dunder method to determine the next value it returns. An implementation could look like this:



class MainIterator:
def __init__(self, iterable):
self.iterable = iterable
self.index = 0

def __next__(self):
if self.index >= self.iterable.length:
raise StopIteration

self.index += 1
return self.iterable.items[self.index - 1]


What I am basically doing is creating a reference to the calling iterable and saving it in self.iterable. Now every time the __next__ dunder method is called, it returns an element of the array, until the iterator is exhausted. This is indicated by raising StopIteration.



You do not see such an implementation very often, as these two classes are often merged into a single class. I just wanted to demonstrate that it is possible to separate the two. The result is what @rbricheno already posted:



class Main:
def __init__(self):
self.items = list(range(1, 8))
self.length = len(self.items)

def __iter__(self):
self.index = 0
return self

def __next__(self):
if self.index >= self.length:
raise StopIteration

self.index += 1
return self.items[self.index - 1]


The difference is that __init__ returns the instance itself, as the class itself is now iterable and iterator (remember: an iterator has the __next__ dunder method, and an iterable has a __iter__ dunder method that returns an iterator).



The last interesting bit is, when these dunder methods are called. Actually, when using the for in syntax, it is syntactic sugar for:



a = Main()

## recreating the for in loop

itr = a.__iter__()

while True:
try:
print(itr.__next__())
except StopIteration:
break


You initialize the iterator first, and __next__ returns a value until the iterator is exhausted.



EDIT:



You should really read my post again. It is NOT good practice to separate the iterator. It's just to demonstrate how they work internally. Also, please do not define your own dunder methods. That will break your code at some time. I have corrected your dict class below, but I iterate over the pair, not its components.



class Pair:

def __init__(self, key, value):
self.key = key
self.value = value

## you need this to display your class in a meaningful way
def __repr__(self):
return f'__class__.__name__(self.key, self.value)'

class Dictionary:

def __init__(self):
self.items =
self.length = len(self.items)

def add(self, objects):
self.items.append(objects)
self.length += 1

def __iter__(self):
self.index = 0
return self

def __next__(self):
if self.index >= self.length:
raise StopIteration

self.index += 1
return self.items[self.index - 1]

a = Dictionary()

a.add(Pair('up', 'above'))
a.add(Pair('down', 'below'))

for i in a:
print(i.key)
print(i.value)


Output on my machine:



up
above
down
below





share|improve this answer















This answers only your first question, and might help you with question 2.



Citing from 'Fluent Python' (p. 420):




[...] Objects implementing an __iter__ method returning an iterator are iterable. [...]




That means, you could (in theory) do something like this:



class Main:
def __init__(self):
self.items = list(range(1, 8))
self.length = len(self.items)

def __iter__(self):
return MainIterator(self)


Now, but how does the MainIterator class look like? The iterator just needs a __next__ dunder method to determine the next value it returns. An implementation could look like this:



class MainIterator:
def __init__(self, iterable):
self.iterable = iterable
self.index = 0

def __next__(self):
if self.index >= self.iterable.length:
raise StopIteration

self.index += 1
return self.iterable.items[self.index - 1]


What I am basically doing is creating a reference to the calling iterable and saving it in self.iterable. Now every time the __next__ dunder method is called, it returns an element of the array, until the iterator is exhausted. This is indicated by raising StopIteration.



You do not see such an implementation very often, as these two classes are often merged into a single class. I just wanted to demonstrate that it is possible to separate the two. The result is what @rbricheno already posted:



class Main:
def __init__(self):
self.items = list(range(1, 8))
self.length = len(self.items)

def __iter__(self):
self.index = 0
return self

def __next__(self):
if self.index >= self.length:
raise StopIteration

self.index += 1
return self.items[self.index - 1]


The difference is that __init__ returns the instance itself, as the class itself is now iterable and iterator (remember: an iterator has the __next__ dunder method, and an iterable has a __iter__ dunder method that returns an iterator).



The last interesting bit is, when these dunder methods are called. Actually, when using the for in syntax, it is syntactic sugar for:



a = Main()

## recreating the for in loop

itr = a.__iter__()

while True:
try:
print(itr.__next__())
except StopIteration:
break


You initialize the iterator first, and __next__ returns a value until the iterator is exhausted.



EDIT:



You should really read my post again. It is NOT good practice to separate the iterator. It's just to demonstrate how they work internally. Also, please do not define your own dunder methods. That will break your code at some time. I have corrected your dict class below, but I iterate over the pair, not its components.



class Pair:

def __init__(self, key, value):
self.key = key
self.value = value

## you need this to display your class in a meaningful way
def __repr__(self):
return f'__class__.__name__(self.key, self.value)'

class Dictionary:

def __init__(self):
self.items =
self.length = len(self.items)

def add(self, objects):
self.items.append(objects)
self.length += 1

def __iter__(self):
self.index = 0
return self

def __next__(self):
if self.index >= self.length:
raise StopIteration

self.index += 1
return self.items[self.index - 1]

a = Dictionary()

a.add(Pair('up', 'above'))
a.add(Pair('down', 'below'))

for i in a:
print(i.key)
print(i.value)


Output on my machine:



up
above
down
below






share|improve this answer














share|improve this answer



share|improve this answer








edited Nov 13 '18 at 15:40

























answered Nov 13 '18 at 14:20









DocDrivenDocDriven

1,1972620




1,1972620












  • Hi @DocDriven, thank you for your reply, it really helped me understand more in depth. ** I have finally written the working code for Part 2** . If you have time, would be great if you have look over it. I have posted it as an answer.

    – lynxx
    Nov 13 '18 at 15:12

















  • Hi @DocDriven, thank you for your reply, it really helped me understand more in depth. ** I have finally written the working code for Part 2** . If you have time, would be great if you have look over it. I have posted it as an answer.

    – lynxx
    Nov 13 '18 at 15:12
















Hi @DocDriven, thank you for your reply, it really helped me understand more in depth. ** I have finally written the working code for Part 2** . If you have time, would be great if you have look over it. I have posted it as an answer.

– lynxx
Nov 13 '18 at 15:12





Hi @DocDriven, thank you for your reply, it really helped me understand more in depth. ** I have finally written the working code for Part 2** . If you have time, would be great if you have look over it. I have posted it as an answer.

– lynxx
Nov 13 '18 at 15:12











0














Thats what I came up with:



class Pair():
def __init__(self, key, value):
self.key = key
self.value = value


class dictionary():
def __init__(self):
self.items =

def add(self, objects):
self.items.append(objects)

def __keys__(self):
return iterator(self, 'keys')

def __values__(self):
return iterator(self, 'values')

class iterator():
def __init__(self, to_be_iterated , over_what):
self.to_be_iterated = to_be_iterated
self.over_what = over_what


def __iter__(self):
self.index = -1
return self

def __next__(self):
self.index += 1
if self.over_what == 'keys':
try:
return self.to_be_iterated.items[self.index].key
except Exception:
raise StopIteration

elif self.over_what == 'values':
try:
return self.to_be_iterated.items[self.index].value
except Exception:
raise StopIteration


collins = dictionary()

collins.add(Pair('up', 'above'))
collins.add(Pair('down', 'below'))

for i in collins.__keys__():
print(i)

for i in collins.__values__():
print(i)





share|improve this answer


















  • 1





    I am not sure if you got the idea completely, however, I edited my answer with a possible solution to your problem.

    – DocDriven
    Nov 13 '18 at 15:40











  • I got it! thanks heaps you both.

    – lynxx
    Nov 13 '18 at 16:23















0














Thats what I came up with:



class Pair():
def __init__(self, key, value):
self.key = key
self.value = value


class dictionary():
def __init__(self):
self.items =

def add(self, objects):
self.items.append(objects)

def __keys__(self):
return iterator(self, 'keys')

def __values__(self):
return iterator(self, 'values')

class iterator():
def __init__(self, to_be_iterated , over_what):
self.to_be_iterated = to_be_iterated
self.over_what = over_what


def __iter__(self):
self.index = -1
return self

def __next__(self):
self.index += 1
if self.over_what == 'keys':
try:
return self.to_be_iterated.items[self.index].key
except Exception:
raise StopIteration

elif self.over_what == 'values':
try:
return self.to_be_iterated.items[self.index].value
except Exception:
raise StopIteration


collins = dictionary()

collins.add(Pair('up', 'above'))
collins.add(Pair('down', 'below'))

for i in collins.__keys__():
print(i)

for i in collins.__values__():
print(i)





share|improve this answer


















  • 1





    I am not sure if you got the idea completely, however, I edited my answer with a possible solution to your problem.

    – DocDriven
    Nov 13 '18 at 15:40











  • I got it! thanks heaps you both.

    – lynxx
    Nov 13 '18 at 16:23













0












0








0







Thats what I came up with:



class Pair():
def __init__(self, key, value):
self.key = key
self.value = value


class dictionary():
def __init__(self):
self.items =

def add(self, objects):
self.items.append(objects)

def __keys__(self):
return iterator(self, 'keys')

def __values__(self):
return iterator(self, 'values')

class iterator():
def __init__(self, to_be_iterated , over_what):
self.to_be_iterated = to_be_iterated
self.over_what = over_what


def __iter__(self):
self.index = -1
return self

def __next__(self):
self.index += 1
if self.over_what == 'keys':
try:
return self.to_be_iterated.items[self.index].key
except Exception:
raise StopIteration

elif self.over_what == 'values':
try:
return self.to_be_iterated.items[self.index].value
except Exception:
raise StopIteration


collins = dictionary()

collins.add(Pair('up', 'above'))
collins.add(Pair('down', 'below'))

for i in collins.__keys__():
print(i)

for i in collins.__values__():
print(i)





share|improve this answer













Thats what I came up with:



class Pair():
def __init__(self, key, value):
self.key = key
self.value = value


class dictionary():
def __init__(self):
self.items =

def add(self, objects):
self.items.append(objects)

def __keys__(self):
return iterator(self, 'keys')

def __values__(self):
return iterator(self, 'values')

class iterator():
def __init__(self, to_be_iterated , over_what):
self.to_be_iterated = to_be_iterated
self.over_what = over_what


def __iter__(self):
self.index = -1
return self

def __next__(self):
self.index += 1
if self.over_what == 'keys':
try:
return self.to_be_iterated.items[self.index].key
except Exception:
raise StopIteration

elif self.over_what == 'values':
try:
return self.to_be_iterated.items[self.index].value
except Exception:
raise StopIteration


collins = dictionary()

collins.add(Pair('up', 'above'))
collins.add(Pair('down', 'below'))

for i in collins.__keys__():
print(i)

for i in collins.__values__():
print(i)






share|improve this answer












share|improve this answer



share|improve this answer










answered Nov 13 '18 at 15:09









lynxxlynxx

235




235







  • 1





    I am not sure if you got the idea completely, however, I edited my answer with a possible solution to your problem.

    – DocDriven
    Nov 13 '18 at 15:40











  • I got it! thanks heaps you both.

    – lynxx
    Nov 13 '18 at 16:23












  • 1





    I am not sure if you got the idea completely, however, I edited my answer with a possible solution to your problem.

    – DocDriven
    Nov 13 '18 at 15:40











  • I got it! thanks heaps you both.

    – lynxx
    Nov 13 '18 at 16:23







1




1





I am not sure if you got the idea completely, however, I edited my answer with a possible solution to your problem.

– DocDriven
Nov 13 '18 at 15:40





I am not sure if you got the idea completely, however, I edited my answer with a possible solution to your problem.

– DocDriven
Nov 13 '18 at 15:40













I got it! thanks heaps you both.

– lynxx
Nov 13 '18 at 16:23





I got it! thanks heaps you both.

– lynxx
Nov 13 '18 at 16:23

















draft saved

draft discarded
















































Thanks for contributing an answer to Stack Overflow!


  • Please be sure to answer the question. Provide details and share your research!

But avoid


  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53280063%2fconfusions-about-python-iterators%23new-answer', 'question_page');

);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

Kleinkühnau

Makov (Slowakei)

Deutsches Schauspielhaus