How does this class implement the "__iter__" method without implementing "next"? - python

I have the following code in django.template:
class Template(object):
def __init__(self, template_string, origin=None, name='<Unknown Template>'):
try:
template_string = smart_unicode(template_string)
except UnicodeDecodeError:
raise TemplateEncodingError("Templates can only be constructed from unicode or UTF-8 strings.")
if settings.TEMPLATE_DEBUG and origin is None:
origin = StringOrigin(template_string)
self.nodelist = compile_string(template_string, origin)
self.name = name
def __iter__(self):
for node in self.nodelist:
for subnode in node:
yield subnode
def render(self, context):
"Display stage -- can be called many times"
return self.nodelist.render(context)
The part I am confused about is below. How does this __iter__ method work? I can't find any corresponding next method.
def __iter__(self):
for node in self.nodelist:
for subnode in node:
yield subnode
This is the only way that I know how to implement __iter__:
class a(object):
def __init__(self,x=10):
self.x = x
def __iter__(self):
return self
def next(self):
if self.x > 0:
self.x-=1
return self.x
else:
raise StopIteration
ainst = a()
for item in aisnt:
print item
In your answers, try to use code examples rather than text, because my English is not very good.

From the docs:
If a container object’s __iter__()
method is implemented as a generator,
it will automatically return an
iterator object (technically, a
generator object) supplying the
__iter__() and __next__() methods.
Here is your provided example using a generator:
class A():
def __init__(self, x=10):
self.x = x
def __iter__(self):
for i in reversed(range(self.x)):
yield i
a = A()
for item in a:
print(item)

That __iter__method returns a python generator (see the documentation), as it uses the yield keyword.
The generator will provide the next() method automatically; quoting the documentation:
What makes generators so compact is that the __iter__() and next() methods are created
automatically.
EDIT:
Generators are really useful. If you are not familiar with them, I suggest you readup on them, and play around with some test code.
Here is some more info on iterators and generators from StackOverflow.

Related

Why I can't inherit map in python?

I want to write a self defined class that inherit map class.
class MapT(map):
def __init__(self, iii):
self.obj = iii
But I can't initialize it.
# Example init object
ex = map(None,["","1","2"])
exp1 = MapT(ex)
# TypeError: map() must have at least two arguments.
exp1 = MapT(None,ex)
# TypeError: __init__() takes 2 positional arguments but 3 were given
How do I create a class that inherit map in python?
Or why I can't inherit map in python?
add
What I want to achieve is add custom method for iterable object
def iter_z(self_obj):
class IterC(type(self_obj)):
def __init__(self, self_obj):
super(iterC, self).__init__(self_obj)
self.obj = self_obj
def map(self, func):
return iter_z(list(map(func, self.obj))) # I want to remove "list" here, but I can't. Otherwise it cause TypeError
def filter(self, func):
return iter_z(list(filter(func, self.obj))) # I want to remove "list" here, but I can't. Otherwise it cause TypeError
def list(self):
return iter_z(list(self.obj))
def join(self, Jstr):
return Jstr.join(self)
return IterC(self_obj)
So that I can do this:
a = iter_z([1,3,5,7,9,100])
a.map(lambda x:x+65).filter(lambda x:x<=90).map(lambda x:chr(x)).join("")
# BDFHJ
Instead of this:
"".join(map(lambda x:chr(x),filter(lambda x:x<=90,map(lambda x:x+65,a))))
You shouldn't be inheriting from the object you're wrapping. That's because your API is different from that type, and there's no good way to ensure you can build a new instance of the class properly. Your map situation is an example of this, your __init__ expects a different number of argumetns than map.__new__ does, and there's no good way to rationalize them.
Instead of inheriting from the class, just wrap around it. This might limit the API of the type that can be used, but you're mostly focused on the iterator protocol, so probably __iter__ and __next__ are all you need:
class IterZ:
def __init__(self, iterable):
self.iterator = iter(iterable)
def __iter__(self):
return self
def __next__(self):
return next(self.iterator)
def map(self, func):
return IterZ(map(func, self.iterator))
def filter(self, func):
return IterZ(filter(func, self.iterator))
def join(self, Jstr):
return Jstr.join(self.iterator)

Enable Python Class to support for loop through an internal iterable member variable

from sortedcontainers import SortedSet
class BigSet(object):
def __init__(self):
self.set = SortedSet()
self.current_idx = -1
def __getitem__(self, index):
try:
return self.set[index]
except IndexError as e:
print('Exception: Index={0} len={1}'.format(index, len(self.ord_set)))
raise StopIteration
def add(self, element):
self.set.add(element)
def __len__(self):
return len(self.set)
def __iter__(self):
self.current_idx = -1
return self
def __next__(self):
self.current_idx += 1
if self.current_idx == len(self.set):
raise StopIteration
else:
return self.set[self.current_idx]
def main():
big = BigSet()
big.add(1)
big.add(2)
big.add(3)
for b in big:
print(b)
for b2 in big:
print(b2)
if __name__ == "__main__":
main()
I have a class that embeds an iterable member variable named self.set and I would like to enable this class to support for loop. The above is the code that I wrote for the purpose. However, I think there must be a better way to do this task easier since the class has an iterable member already.
Question> Is there a way that I can delegate the job to the embedded self.set? Also, I think there maybe a good way to implement the __getitem__ too.
Thank you
I am almost certain you do not want your container to be an iterator. So, it should not implement __next__. Instead, it should be iterable, so it only needs to implement __iter__. In that case, if you want to delegate to the iterable member:
def __iter__(self):
return iter(self.set)
And remove your __next__ method.

__iter__ (iterator and generator)

I read a book about advanced topics in python.
The author was trying to explain the generator.
This was his example to explain:
class rev:
def __init__(self,data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def __next__(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]
def main():
reve = rev('zix')
for i in reve:
print(i)
if __name__=='__main__':
main()
The main idea of this code is to reverse generators.
The output is :
x
i
z
The thing I found hard to understand is this part:
def __iter__(self):
return self
Can someone explain it to me?
When you do for x in xs, xs has to be an iterable, which means you can get an iterator out of it by iter(xs), which you can do when xs.__iter__() is implemented. An iterator is required to implement __next__(), so that the in operator can consume it one by one by calling next() on it.
Now, in your case
reve = rev("hello") # is an iterable, as there is rev.__iter__()
rev1 = iter(reve) # is an iterator, this is what rev.__iter__() returns
rev2 = next(rev1) # now go on calling next() till you get StopIteration
Type the above snippet in REPL. Run it a few times. You will get a feel for it.
The iterator protocol is comprised of two methods:
__iter__, and
__next__
Also, a requirement is that __iter__ returns self -- so if you have an obj that is an iterator then
obj is iter(obj) is obj.__iter__()
is true.
This is a good thing because it allows us to say iter = iter(obj) and if obj was already an iterator we still have the same object.
Since your class provides an implementation for next() it returns self so the caller will call that when looping over your object. In contrast, if you only wanted to wrap a data structure that already provides an implementation of __iter__ and next (e.g. list), you could return self.data.__iter__() from your class. In that case the caller would call next() defined on that object when doing a loop or list comprehension let's say.
class rev:
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def next(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]
class rev2:
def __init__(self, data):
self.data = data
def __iter__(self):
return self.data.__iter__()
def main():
for i in rev('zix'): # str doesn't provide implementation for __iter__
print(i)
for i in rev2(['z', 'i', 'x']): # list does so no need to implement next
print(i)

issubclass of abstract base class Sequence

This list shows what methods you need to implement for your class to be "regarded" as Sequence: __getitem__, __len__, __contains__, __iter__, __reversed__, index, and count. So why does this minimal implementation does not work, i.e. why issubclass(S, Sequence) is False?
from collections import *
class S(object):
def __getitem__(self, item):
raise IndexError
def __len__(self):
return 0
def __contains__(self, item):
return False
def __iter__(self):
return iter(())
def __reversed__(self):
return self
def index(self, item):
raise IndexError
def count(self, item):
return 0
issubclass(S, Iterable) # True :-)
issubclass(S, Sized) # True :-)
issubclass(S, Container) # True :-)
issubclass(S, Sequence) # False :-(
Is there an additional method I need to implement that I overlooked? Did I misunderstand abstract base classes? Subclassing Sequence makes issubclass return True of course, but that kinda defeats the idea behind abc, doesn't it?
Use the source, Luke!
Sequence does not implement its own __subclasshook__, and all the implementations of __subclasshook__ from the parents of Sequence have checks like this:
class Iterable:
...
#classmethod
def __subclasshook__(cls, C):
if cls is Iterable: # <<<<
if _hasattr(C, "__iter__"):
return True
return NotImplemented
You can however explicitly register() your class as a Sequence:
Sequence.register(S)
As for the reason why Sequence does not implement __subclasshook__, see issue 16728 (which title was initially "collections.abc.Sequence shoud provide __subclasshook__"). The issue can be summarized by saying that a sequence can be many things, depending on the needs of who uses it:
Many algorithms that require a sequence only need __len__ and __getitem__. [...] collections.abc.Sequence is a much richer interface.

Implicitly binding callable objects to instances

I have this code:
class LFSeq: # lazy infinite sequence with new elements from func
def __init__(self, func):
self.evaluated = []
self.func = func
class __iter__:
def __init__(self, seq):
self.index = 0
self.seq = seq
def next(self):
if self.index >= len(self.seq.evaluated):
self.seq.evaluated += [self.seq.func()]
self.index += 1
return self.seq.evaluated[self.index - 1]
And I explicitely want that LFSeq.__iter__ becomes bounded to an instance of LFSeq like any other user-defined function would have been.
It doesn't work this way though because only user-defined functions are bounded and not classes.
When I introduce a function decorator like
def bound(f):
def dummy(*args, **kwargs):
return f(*args, **kwargs)
return dummy
then I can decorate __iter__ by it and it works:
...
#bound
class __iter__:
...
This feels somehow hacky and inconsistent however. Is there any other way? Should it be that way?
I guess yes because otherwise LFSeq.__iter__ and LFSeq(None).__iter__ wouldn't be the same object anymore (i.e. the class object). Maybe the whole thing about bounded functions should have been syntactic sugar instead of having it in the runtime. But then, on the other side, syntactic sugar shouldn't really dependent on content. I guess there has to be some tradeoff at some place.
The easiest solution for what you are trying to do is to define your __iter__() method as a generator function:
class LFSeq(object):
def __init__(self, func):
self.evaluated = []
self.func = func
def __iter__(self):
index = 0
while True:
if index == len(self.evaluated):
self.evaluated.append(self.func())
yield self.evaluated[index]
index += 1
Your approach would have to deal with lots of subtleties of the Python object model, and there's no reason to go that route.
In my opinion, the best solution is #Sven one, no doubt about it. That said, what you are trying to do really seems extremely hackish - I mean, to define __iter__ as a class. It will not work because declaring a class inside another one is not like defining a method, but instead it is like defining an attribute. The code
class LFSeq:
class __iter__:
roughly equivalent to an attribution that will create a class field:
class LFSeq:
__iter__ = type('__iter__', (), ...)
Then, every time you define an attribute inside a class, this is bound to the class itself, not to specific instances.
I think you should follow #Sven solution, but if you really want to define a class for any other reason, it seems you are lucky, because your generator class does not depend upon nothing from the LFSeq instance itself. Just define the iterator class outside:
class Iterator(object):
def __init__(self, seq):
self.index = 0
self.seq = seq
def next(self):
if self.index >= len(self.seq.evaluated):
self.seq.evaluated += [self.seq.func()]
self.index += 1
return self.seq.evaluated[self.index - 1]
and instantiate it inside LFSeq.__iter__() method:
class LFSeq(object): # lazy infinite sequence with new elements from func
def __init__(self, func):
self.evaluated = []
self.func = func
def __iter__(self):
return Iterator(self)
If you eventually need to bind the iterator class to the instance, you can define the iterator class inside LFSeq.__init__(), put it on a self attribute and instantiate it in LFSeq.__iter__():
class LFSeq(object): # lazy infinite sequence with new elements from func
def __init__(self, func):
lfseq_self = self # For using inside the iterator class
class Iterator(object): # Iterator class defined inside __init__
def __init__(self):
self.index = 0
self.seq = lfseq_self # using the outside self
def next(self):
if self.index >= len(self.seq.evaluated):
self.seq.evaluated += [self.seq.func()]
self.index += 1
return self.seq.evaluated[self.index - 1]
self.iterator_class = Iterator # setting the itrator
self.evaluated = []
self.func = func
def __iter__(self):
return self.iterator_class() # Creating an iterator
As I have said, however, #Sven solution seems finer. I just answered do try to explain why your code did not behaved as you expected and to provide some info about to do what you want to do - which may be useful sometimes nonetheless.

Categories

Resources