I am writing range generator. Depending on whether the step is positive or negative, __next__ should compare start and end with <= or >=. I would like to check if step is positive once, and define __next__ depending on that comparison. This is what I did (to simplify, allow only exactly three inputs):
class MyRange:
def __init__(self, *args):
self.min, self.max, self.step = args[0] - args[2], args[1], args[2]
if self.step > 0:
self.__next__ = self.next_pos
else:
self.__next__ = self.next_neg
def __iter__(self):
return self
def __next__(self):
pass
def next_pos(self):
self.min += self.step
if self.min >= self.max:
raise StopIteration
return self.min
def next_neg(self):
self.min += self.step
if self.min <= self.max:
raise StopIteration
return self.min
If __next__ is not defined outside of __init__, then I get an error:
TypeError: iter() returned non-iterator of type 'MyRange'.
I assumed that defining it as above would first initialise it according to the def __next__(self), and then overwrite in __init__, but the order is the opposite. Why is it not being overloaded? How can I overwrite __next__ depending on the step when creating an instance?
I know that I could store next_pos or next_neg inside self.my_comparison, and define
def __next__(self):
return self.my_comparison()
but I am wondering how I can overload __next__ inside __init__ and why it doesn't work in my case.
Python dunder (or magic) methods are always looked on the class of an object, and never picked from the instance. Assigning an ordinary function to an instance that will work as a method will work, although it won't get the
selfargument injected by the Python runtime - but dunder methods are called by the runtime itself, and they are picked from the appropriate class slot for each functionality.That is easy to work around: you can use your class defined
__next__method to call another, instance bound callable, as an ordinary, "user side" call. You can then make this binding in__init__as you had devised:Note that I wrote above that callables bound to the instance in this way won't get "self" injected by Python - however, this code is not assigining an ordinary function there: when we write
self.user_method = self.method- theself.methodpart creates a "bound method" - a callable that will already insert theselfto the underlying function (unbound method) when called. And that bound method is then assigned as a common instance attribute. When we executeself.usermethod(), firstusermethodis retrieved from the instance, with no transformation mechanism (such asselfarg injection) and then called.