docs on for-loop with no __iter__?

Paul McGuire ptmcg at austin.rr._bogus_.com
Sat Sep 4 23:23:21 CEST 2004


"Andrew Dalke" <adalke at mindspring.com> wrote in message
news:bMp_c.7491$w%6.1810 at newsread1.news.pas.earthlink.net...
<snip>
>
> Looking at the language reference from CVS, I found
>   http://www.python.org/dev/doc/devel/ref/for.html
>
> It states
>
> ]  The suite is then executed once for each item in
> ] the sequence, in the order of ascending indices.
>
> That implies the sequence is indexed, yes?  But if
> the sequence implements __iter__ then there's no
> possibly no underlying idea of 'index'.
>
> Should this be fixed?
>
> Andrew
> dalke at dalkescientific.com
Section 7.3 (from the link given above) gives the syntax for "for" as:

for_stmt  ::=  "for" target_list "in" expression_list ":" suite
    ["else" ":" suite]

and then begins describing the component syntax elements as, "The expression
list is evaluated once; it should yield a sequence."  This seems to be a bit
dated, since expression_list could also be a generator or iterator.

Additionally, "for" uses an adaptive method to try to simulate an iterator
if no __iter__ method is provided, by successively calling __getitem__ until
IndexError is raised (this error gets silently absorbed within this
pseudo-iterator).

Here is a simple test class:  (I also implemented __len__ thinking that it
would be used to limit the calls to __getitem__, but we can see from the
output that it is never called - instead __getitem__ gets called one time
too many, telling the pseudo-iterator that there are no more entries).

class A(object):
    def __init__(self,lst):
        self.list_ = lst[:]

    def __len__(self):
        print self.__class__.__name__+".__len__"
        return len(self.list_)

    def __getitem__(self,i):
        print self.__class__.__name__+".__getitem__"
        return self.list_[i]

class A_with_iter(A):
    def __iter__(self):
        print self.__class__.__name__+".__iter__"
        return iter(self.list_)


for cls in (A, A_with_iter):

    a = cls([1,2,3])

    print "iterate over %s" % cls.__name__
    for i in a:
        print i

    print

output:
iterate over A
A.__getitem__
1
A.__getitem__
2
A.__getitem__
3
A.__getitem__

iterate over A_with_iter
A_with_iter.__iter__
1
2
3


Note that this is the basis for the recently reported bugs in email.Message
and cgi.FieldStorage.  email.Message does not implement __iter__, and its
__getitem__ method assumes that items will be retrieved like keyed
dictionary lookups, not using integer sequence access.  So when __getitem__
calls .lower() on the input string, Python throws an error - you can't do
lower() on an int.

> >>> em = email.message_from_file(open('MAILMESSAGE'))
> >>> for i in em:
> ... print i
> ...
> Traceback (most recent call last):
> File "<stdin>", line 1, in ?
> File "/usr/lib/python2.3/email/Message.py",
> line 304, in __getitem__
> File "/usr/lib/python2.3/email/Message.py",
> line 370, in get
> AttributeError: 'int' object has no attribute 'lower'

-- Paul





More information about the Python-list mailing list