2
\$\begingroup\$

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
\$\endgroup\$
4
  • 3
    \$\begingroup\$ 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. \$\endgroup\$ – FMc Apr 25 at 17:25
  • \$\begingroup\$ @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? \$\endgroup\$ – bobinthebox Apr 25 at 18:37
  • \$\begingroup\$ 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). \$\endgroup\$ – RootTwo Apr 25 at 19:36
  • \$\begingroup\$ 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(). \$\endgroup\$ – FMc Apr 25 at 19:36
2
\$\begingroup\$

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.

\$\endgroup\$
6
  • \$\begingroup\$ Thanks for your answer, I got the point! So the signature of my_range should be my_range(start, stop,step) too, right? \$\endgroup\$ – bobinthebox Apr 26 at 6:35
  • \$\begingroup\$ @bobinthebox yes, that is my interpretation of the problem statement :) \$\endgroup\$ – RGS Apr 26 at 6:37
  • \$\begingroup\$ Thanks, I'll try to fix this :-) @RGS \$\endgroup\$ – bobinthebox Apr 26 at 6:37
  • \$\begingroup\$ @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? \$\endgroup\$ – RGS Apr 26 at 6:39
  • \$\begingroup\$ I agree, I should use default arguments to achieve this behaviour, right? @RGS \$\endgroup\$ – bobinthebox Apr 26 at 7:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.