# Implement a range behaviour in Python using iterators

I've been assigned the following Python homework:

Implement myrange that acts like range using iterators. Define a function myrangeand define a class MyRange.

We've just seen iterators, so I think I'm asked to use them. I am not required to write a sophisticated code, but only something that allows the range-for loop syntax.

Professor said that, roughly, an iterator is an object for which the dunder method __next__ is provided. I've seen there are similar questions here. However, none of them is defining the __next__ method in a class.

So, here's what I did: first I implemented MyRange class and then my range. After that, I did two tests.

Question: It works, but I'd like to be sure from you if I solved correctly the question, I mean, if this is what I was supposed to do :-) As I said, I've just seen what is an iterator.

class MyRange():

def __init__(self,data):
self.data = data
self.check()
self.index = -1

def check(self):
assert len(self.data)>0, "Not enough data"

def __iter__(self):
return self

def __next__(self):
if self.index == len(self.data)-1:
raise StopIteration
self.index= self.index+1
return self.data[self.index]

def my_range(*args):
return MyRange((args))

print("Tests using MyRange class \n")

for i in MyRange((1,2,3,4)):
print(i)

print("Tests with range for loop \n")
for i in my_range(1,2,3,4,5,"Hello"):
print(i)

r = MyRange((1,2,3,4,"Last Value"))
print(next(r))
print(next(r))
print(next(r))
print(next(r))
print(next(r))
print(next(r))


I'll show here the output, which seems the correct one:

1
2
3
4
Tests with range for loop

1
2
3
4
5
Hello
1
2
3
4
Last Value
Traceback (most recent call last):
File "Untitled3.py", line 45, in <module>
print(next(r)) #<-- Raises StopIteration
File "Untitled3.py", line 16, in __next__
raise StopIteration
StopIteration

• If myrange() is supposed to act like range(), shouldn't it take similar arguments: start, stop, and step? It doesn't seem like your implementation is following the instructions, but you obviously have more context than I do. A range is a device to generate integers (integers often used to index into a data collection), but you have implemented myrange() to take the data collection itself as an argument. Perhaps you can edit your question to clarify.
– FMc
Apr 25 at 17:25
• @FMc Unfortunately the text of the assignment was the short one I wrote at the beginning of my post. So far what we know is only that we need to use __next__ in our class. I think I can fix this point. For the moment, assuming that the class MyRange is correct, do you think that my_range is implemented correctly? Apr 25 at 18:37
• The assignment seems to be asking to implement range two different ways (1) as a function (e.g. using yield in a loop), and (2) as a class (e.g. having __iter__ and __next__ methods). Apr 25 at 19:36
• No, based on my reading of the assignment, I think both MyRange and my_range() are incorrect. You have successfully implemented an iterable object (MyRange), but it is not an iterable object that behaves at all like range().
– FMc
Apr 25 at 19:36

# You have an iterator

Your code implements an iterator, you are right. You have defined a __next__ method and it works well.

I would tweak your code slightly, though:

• remove the check because iterators can be empty and that is okay;
• fix minor spacing issues around operators, etc;
• use augmented assignment to increment;
• change order of statements in __next__ to be more idiomatic.

All in all, MyRange would end up looking like this:

class MyRange():
def __init__(self,data):
self.data = data
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]


In particular, the changes to __next__ are because you start by setting self.index to the value of the index you would like to read. Then, just before reading the value from data, you ensure you can actually use that index and raise StopIteration if you can't.

# range is not any iterator

However, the "problem statement" says to implement an iterator that behaves like range, and as brought to your attention in the comments, range is a very specific iterator that can take up to 3 arguments: start, stop, and step.

Take a look at the docs for the range function here.

Having said that, solving this "issue" does not need to be very difficult. You could do something as simple as having my_range take the three arguments, generate the data you need, and then feed it into MyRange to iterate, but that seems like a waste.

What I think is more or less the intended approach, is to define MyRange to expect the start, stop, and step values, and the my_range function is what the user calls, which then fills in the start, stop, and step values that are used by default and calls MyRange.

E.g.,

• my_range(10) would call MyRange(0, 10, 1);
• my_range(4, 12) would call MyRange(4, 12, 1);
• my_range(0, 13, -3) would call MyRange(0, 13, -3).

(In the examples above, I assumed you changed MyRange to do what I described.)

Therefore, just for the sake of clarity: what would be happening is that the __next__ method would use the three arguments start, stop, and step (and possibly other auxiliary variables you create) to compute the successive values, instead of generating all of them at once and then returning one by one.

• Thanks for your answer, I got the point! So the signature of my_range should be my_range(start, stop,step) too, right? Apr 26 at 6:35
• @bobinthebox yes, that is my interpretation of the problem statement :)
– RGS
Apr 26 at 6:37
• Thanks, I'll try to fix this :-) @RGS Apr 26 at 6:37
• @bobinthebox good luck! But sorry, just to be clear, probably your my_range function also has to work with a single argument, my_range(stop) and two arguments, my_range(start, stop), just like range does, yeah?
– RGS
Apr 26 at 6:39
• I agree, I should use default arguments to achieve this behaviour, right? @RGS Apr 26 at 7:34