I have just learned about iterators in Python however I am having a hard time implementing them.
I am trying to write a class to so that this loop works:
odds = OddNumbers(13)
for i in odds:
print(i)
I want to write an iter() function and next() function to do this.
So far I have:
class OddNumbers:
def __init__(self, number):
self.number = number
def __iter__(self):
return self
def __next__(self):
current = self.number
if self.number%2 == 0:
return current
else:
raise StopIteration
But at the moment this is returning nothing. I expect the output to be
1
3
5
7
9
11
13
Help?
Your object needs to keep track of its state and update it when __next__ is called.
class OddNumbers(object):
def __init__(self, number):
self.current = ...
self.number = number
def __iter__(self):
return self
def __next__(self):
# Update self.current
# If the limit has been reached, raise StopIteration
# Otherwise, return something
You need another variable to track the current number:
def __init__(self, number):
self.number = number
self.current = 1
Then you need to compare it with the ending number, and maybe increment it:
def __next__(self):
if self.current > self.number:
raise StopIteration
current = self.current
self.current += 2
return current
There is probably a much cleaner way of doing this, but here is a quick stab:
class OddNumbers:
def __init__(self, number):
self.number = number
def __iter__(self):
self.current = self.number
return self
def __next__(self):
if self.current > 0:
if self.current % 2 == 0:
self.current -= 1
self.current -= 1
return self.current + 1
raise StopIteration
This will give you an iterator-like object which provides even or odd numbers. However, it won't satisfy your for loop semantics as it is not a true iterator.
class NumberIterator(object):
"""Returns simple even/odd number iterators."""
def __init__(self, current):
self.current = current
def next(self):
self.current += 2
return self.current
#classmethod
def getOddIterator(cls):
return cls(-1) # start at 1
#classmethod
def getEvenIterator(cls):
return cls(0) # start at 2
odd_numbers = NumberIterator.getOddIterator()
even_numbers = NumberIterator.getEvenIterator()
odd_numbers.next() # Returns 1
odd_numbers.next() # Returns 3
Related
I am new to programming so this might come off as a silly question but can you help me with this code..... I am trying to print just the first ten numbers after the number 'self.num' but somehow it is just entering an infinite loop which I am having to terminate
class TopTen:
def __init__(self, num):
self.num = num
def __iter__(self):
return self
def __next__(self):
if self.num <= self.num+10:
val= self.num
self.num += 1
return val
else:
raise StopIteration
values = TopTen(5)
for i in values:
print(i)
You can implement it like this:
class TopTen:
def __init__(self, num):
self.num = num
self.maxNum = num+10
def __iter__(self):
return self
def next(self):
if self.num < self.maxNum:
val = self.num
self.num += 1
return val
else:
raise StopIteration
for i in TopTen(5):
print(i)
# Prints
# 5
# 6
# 7
# 8
# 9
# 10
# 11
# 12
# 13
# 14
The reason why you have infinite loop is because if self.num <= self.num + 10: will always evaluate to True, as you increase self.num by 1 each time by calling next(), so self.num + 10 will always be higher than self.num.
The condition test if self.num <= self.num+10: in your class will always be true, thats the reason for the infinite loop:
I've started to learn iterators am trying to implement them myself.
I have created a class that should provide numbers within a range from user defined start to user defined end, in an iterable form.
Now my code looks like this:
class Can_be_divided_by_three:
def __init__(self, start, end):
self.start = start
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.start > self.end:
raise StopIteration
item = self.start
self.start += 1
if item % 3 == 0:
return item
iterator = Can_be_divided_by_three(3, 8)
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))
And this is the output:
3
None
None
6
So actually there is output even if the number is not divided by 3 and it is None.
Am I getting this wrong, and if yes, how to get it right? I actually need the only output in the form of number divisible by 3 with iteration capabilities.
Thank you in advance.
As per your logic next method will return the number if the number is divisible by 3 but you have not specified what this function should do if the number is not divisible by three, so try below code:
class Can_be_divided_by_three:
def __init__(self, start, end):
self.start = start
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.start > self.end:
raise StopIteration
item = self.start
self.start += 1
if item % 3 == 0:
return item
else:
return self.__next__()
iterator = Can_be_divided_by_three(3, 8)
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))
Based on John Coleman's comment that you only need to find the smallest multiple of 3, you can achieve the same with this:
def Can_be_divided_by_three(start, end):
while start % 3:
start += 1
for i in range(start, end, 3):
yield i
You only return item if item % 3 == 0.
You should return something else in other scenarios if you want to avoid Nones
The code which you have written is right and would return number divisible by 3 within user defined range. The reason you are finding None in the output is because of the print() function.
Try running the only next(iterator). In Jupyter the iterator returns only 3 & 6
Consider this piece of code, wondering would it be possible to return a value before it gets updated.
class A:
def __init__(self):
self.n = 0
def get_next(self):
return self.n++ # Return its current value. after it gets returned, update n.
a = A()
a.get_next() # return 0
a.get_next() # return 1
This'll work:
def get_next(self):
old_value = self.n
self.n += 1
return old_value
The title pretty much explains the problem. I don't know if there's a practical solution to this or if I'm being too picky over the behavior of my code.
This article was hinting in the right direction, but I never got any code to work.
https://medium.com/#adamshort/python-gems-5-silent-function-chaining-a6501b3ef07e
Here's an example of the functionality that I want:
class Calc:
def __init__(self, n=0):
self.n = n
def add(self, n):
self.n += n
return self
def might_return_false(self):
return False
def print(self):
print(self.n)
return self
w = Calc()
# The rest of the chain after might_return_false should be ignored
addedTwice = w.add(5).might_return_false().add(5).print()
w.print() # Should print 5
print(addedTwice) # Should print False
I think the article meant something more or less like below (but I prefer the other answer using exception, as it's more readable and better testable).
Create a helper class:
class Empty:
def __call__(self, *args, **kwargs):
return self
def __getattr__(self, *args, **kwargs):
return self
def print(self, *args, **kwargs):
return False
and
def might_return_false(self):
return Empty()
Exceptions are a great way to interrupt a chained operation:
class CalcError(Exception):
pass
class Calc:
def __init__(self, n: int = 0):
self.n = n
def add(self, n: int) -> 'Calc':
self.n += n
return self
def might_raise(self) -> 'Calc':
raise CalcError
def __str__(self) -> str:
return str(self.n)
w = Calc()
try:
w.add(5).might_raise().add(5)
addedTwice = True
except CalcError:
addedTwice = False
print(w) # prints 5
print(addedTwice) # prints False
You could also do chains like:
w = Calc()
num_added = 0
try:
w.add(5)
num_added += 1
w.add(5)
num_added += 1
w.might_raise()
w.add(5)
num_added += 1
w.add(5)
num_added += 1
except CalcError:
print(f"Stopped after {num_added} additions")
If you attempt to do this with return instead of raise, you need to check the status at each step of the chain so that you can switch off to some other branch of code (probably via an if block). Raising an exception has the very useful property of immediately interrupting execution, no matter where you are, and taking you straight to the nearest matching except.
I have class Count which takes 3 parameters including self, mystart and myend. It should count from mystart until myend (also reversed) using magic methods __iter__, __next__ and __reversed__. I have implemented all three magic methods. But I am still not sure whether it is the right way to implemnet next and reversed methods. Is it possible that I can call built-in functions next and reversed inside my __next__ and __reversed__ methods or is there any pythonic way?
class Count:
def __init__(self,mystart,myend):
self.mystart=mystart
self.myend=myend
self.current=mystart
self.reverse=[]
def __iter__(self):
"Returns itself as an Iterator Object"
return self
def __next__(self):
if self.current > self.myend:
raise StopIteration
else:
self.current+=1
return self.current-1
def __reversed__(self):
for i in range(self.myend,self.mystart,-1):
self.reverse.append(i)
return self.reverse
obj1=Count(0,10)
print("FOR LOOP")
for i in obj1:
print (i,end=",")
print ("\nNEXT")
obj2=Count(1,4)
print(next(obj2))
print(next(obj2))
print ("Reversed")
print(reversed(obj1))
Now I have done it using yield statement. #jedwards thanks for your tipp.
class Count:
def __init__(self, mystart,myend):
self.mystart = mystart
self.myend = myend
self.current=None
def __iter__(self):
self.current = self.mystart
while self.current < self.myend:
yield self.current
self.current += 1
def __next__(self):
if self.current is None:
self.current=self.mystart
if self.current > self.myend:
raise StopIteration
else:
self.current+=1
return self.current-1
def __reversed__(self):
self.current = self.myend
while self.current >= self.mystart:
yield self.current
self.current -= 1
obj1=Count(0,10)
for i in obj1:
print (i)
obj2=reversed(obj1)
for i in obj2:
print (i)
obj3=Count(0,10)
print (next(obj3))
print (next(obj3))
print (next(obj3))
You are mixing up Iterators and Iterables:
Iterators:
Keep a state associated with their current iteration progress
Implement __next__ to get the next state
Implement __iter__ to return themselves.
Iterables:
Contain (or define with some rule) a collection of elements that can be traversed
Implement __iter__ to return an iterator that can traverse the elements
can implement __reversed__ to return an iterator that goes backwards.
The __reversed__ magic method is:
Called (if present) by the reversed() built-in to implement reverse
iteration. It should return a new iterator object that iterates over
all the objects in the container in reverse order.
So you probably don't want to implement an iterator that can be __reversed__ mid iteration, for example the implementation in your answer means that this code:
x = Count(1,10)
for i in x:
for j in x:
print(i,j)
Will cause an infinite loop, the output is just this pattern repeated:
1 4
1 3
1 2
1 1
the reason for this is because both for loops are changing self.current in opposite directions, the outer loop will increment it by 1 then the inner loop will set it to self.myend and lower it back to 0, then the process repeats.
The only way to properly implement all three magic methods is to use two classes, one for the iterator and one for the iterable:
class _Count_iter:
def __init__(self, start, stop, step=1):
self.current = start
self.step = step
self.myend = stop
def __iter__(self):return self
def __next__(self):
#if current is one step over the end
if self.current == self.myend+self.step:
raise StopIteration
else:
self.current+=self.step
return self.current-self.step
class Count:
def __init__(self, mystart,myend):
self.mystart = mystart
self.myend = myend
def __iter__(self):
return _Count_iter(self.mystart,self.myend,1)
def __reversed__(self):
return _Count_iter(self.myend, self.mystart, -1)