23

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.

bad_coder
  • 11,289
  • 20
  • 44
  • 72
zjm1126
  • 63,397
  • 81
  • 173
  • 221

2 Answers2

41

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)
Pedro Lopes
  • 2,833
  • 1
  • 30
  • 36
Mark Peters
  • 17,205
  • 2
  • 21
  • 17
16

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.

Community
  • 1
  • 1
catchmeifyoutry
  • 7,179
  • 1
  • 29
  • 26
  • Is it meant to be called by client code? I assumed not since it has the __fname__ form – danben Dec 25 '09 at 02:45
  • the --fname-- form where those dashes are underscores – danben Dec 25 '09 at 02:46
  • I have no idea about Django, but in the end it's just python syntax. When a iterator needs to be created for a Template object, its `__iter__(self)` hook will be called. It will then return a Generator object which has the correct next() method. – catchmeifyoutry Dec 25 '09 at 02:52
  • Right - so is there any other way that you're supposed to call it besides using for/in? – danben Dec 25 '09 at 03:01
  • "it" in this case being `__iter__`, as I am currently under the impression that any function beginning with `__` is not meant to be called – danben Dec 25 '09 at 03:02
  • 1
    @danben: You don't call `obj.__iter__()`, you call `iter(obj)`. `iter(obj)` will translate into a call to `obj.__iter__()` if it is defined, much like `Foo()` will translate into a call to `Foo.__init__()` if it is defined. – Daniel Pryden Dec 25 '09 at 03:07
  • 1
    methods/member variables of the form `__x__` usually have a special meaning in python (e.g. `__dict__` and `__builtin__`). The `__iter__` method of an object is called when trying to iterate over the object, such as in a `for .. in ..:` loop. – catchmeifyoutry Dec 25 '09 at 03:12