views:

98

answers:

3

I've already looked at this question: http://stackoverflow.com/questions/1152238/python-iterators-how-to-dynamically-assign-self-next-within-a-new-style-class

but this doesn't help me because I want to iterate of an attribute of the error which is a list (ie, already iterable) without having to use the attribute explicitly. I'm looking to do this:

class SCE(Exception):
    """
    An error while performing SCE functions.
    """
    def __init__(self, value=None):
        """
        Message: A string message or an iterable of strings.
        """
        if value is None:
            self._values = ['A general SCE error has occured.']
        elif isinstance(value, str):
            self._values = [value]
        else:
            self._values = list(value)

    def __iter__(self):
        return self._values

    def __repr__(self):
        return repr(self._values)

However, in the shell I get this:

try:
    raise CSE(['error one', 'error two'])
except CSE, e:
    for i in e:
        print(i)
Traceback (most recent call last):
  File "(stdin)", line 1, in (module)
TypeError: iter() returned non-iterator of type 'list'

I know I could remove the _ from _values and then iterate over e.values but I don't want to do that as it exposes the implementation of my Exception class.

+3  A: 

The __iter__ method should return an iterator object, but you are returning a list object. Use

def __iter__(self):
    return iter(self._values)

instead to fix this. From the documentation for object.__iter__ (my highlighting):

This method is called when an iterator is required for a container. This method should return a new iterator object that can iterate over all the objects in the container.

Torsten Marek
Thanks Torsten Marek. I'll paste my new version that works below.
orokusaki
+1  A: 

__iter__ needs to return an iterator, not a list.

Try this:

    def __iter__(self):
        return iter(self._values)

You could also do:

    def __iter__(self):
        for val in self._values:
            yield val

But I can't really think of a reason you'd need to do that instead of using iter()

pib
+1 vote for you too. Sometimes answers come too fast here.
orokusaki
+3  A: 
def __iter__(self):
    return iter(self._values)

Or a more generic:

def __iter__(self):
    for x in self._values:
        yield x
Lennart Regebro
Actually, it's not more generic, is it? :) Meh, it stays anyway. :)
Lennart Regebro
Ah, thanks. Your second version helps me understand better too. Isn't the second version a generator? Does it perform differently than the first version?
orokusaki
BTW +1 vote. thanks.
orokusaki
@orokusaki: It is a generator, indeed. I'd expect it to be a bit slower, but I haven't tested it. The important difference is that you can do things to x before yielding it.
Lennart Regebro