tags:

views:

146

answers:

3

I'm trying to write a simple GUI front end for Plurk using pyplurk.

I have successfully got it to create the API connection, log in, and retrieve and display a list of friends. Now I'm trying to retrieve and display a list of Plurks.

pyplurk provides a GetNewPlurks function as follows:

  def GetNewPlurks(self, since):
    '''Get new plurks since the specified time.
    Args:
      since: [datetime.datetime] the timestamp criterion.
    Returns:
      A PlurkPostList object or None.
    '''
    offset = jsonizer.conv_datetime(since)
    status_code, result = self._CallAPI('/Polling/getPlurks', offset=offset)
    return None if status_code != 200 else \
           PlurkPostList(result['plurks'], result['plurk_users'].values())

As you can see this returns a PlurkPostList, which in turn is defined as follows:

class PlurkPostList:
  '''A list of plurks and the set of users that posted them.'''
  def __init__(self, plurk_json_list, user_json_list=[]):
    self._plurks = [PlurkPost(p) for p in plurk_json_list]
    self._users = [PlurkUser(u) for u in user_json_list]
  def __iter__(self):
    return self._plurks
  def GetUsers(self):
    return self._users
  def __eq__(self, other):
    if other.__class__ != PlurkPostList: return False
    if self._plurks != other._plurks: return False
    if self._users != other._users: return False
    return True

Now I expected to be able to do something like this:

api = plurk_api_urllib2.PlurkAPI(open('api.key').read().strip(), debug_level=1)
plurkproxy = PlurkProxy(api, json.loads)
user = plurkproxy.Login('my_user', 'my_pass')
ps = plurkproxy.GetNewPlurks(datetime.datetime(2009, 12, 12, 0, 0, 0))
print ps
for p in ps:
  print str(p)

When I run this, what I actually get is:

<plurk.PlurkPostList instance at 0x01E8D738>

from the "print ps", then:

    for p in ps:
TypeError: __iter__ returned non-iterator of type 'list'

I don't understand - surely a list is iterable? Where am I going wrong - how do I access the Plurks in the PlurkPostList?

+6  A: 

When you define your own __iter__ method, you should realize that that __iter__ method should return an iterator, not an iterable. You are returning a list, not an iterator to a list, so it fails. You can fix it by doing return iter(self._plurks), for example.

If you wanted to do something a little more complex, like process each item in self._plurks as it's being iterated over, the usual trick is to make your __iter__ method be a generator. That way, the returnvalue of the call to __iter__ is the generator, which is an iterator:

def __iter__(self):
    for item in self._plurks:
        yield process(item)
Thomas Wouters
Thank you, this works perfectly. The definition of PlurkPostList class including the __iter__ function came directly from PyPlurk, so I blithely assumed it was OK and my own calling of it was deficient. I also hadn't cottoned on to the distinction between an interator and an iterable, so that was very useful. Thanks.
Vicky
+5  A: 

The __iter__ method should return an object which implements the next() method.

A list does not have a next() method, but it has an __iter__ method, which returns a listiterator object. The listiterator object has a next() method.

You should write:

def __iter__(self):
    return iter(self._plurks)
Jeremy
A: 

As an alternative, you can also define the next() function and have __iter__() return self. See http://stackoverflow.com/questions/19151/build-a-basic-python-iterator for a nice example.

rioch
That would make multiple iterations over the same object cumbersome, however.
Thomas Wouters
How would it be cumbersome? Am I missing something? I was under the impression that using generators, as you did, achieved the same as using next(). Please, correct me if I'm wrong.
rioch
When you make an object itself an iterator, it would have to maintain the state of the iteration. It would have to remember how often the `next` method had been called. When you use a generator the generator does that for you. When you implement an iterator you need to store that state somewhere on the iterator. When an object is its own iterator (like, for example, a `file` is) then the object can only be iterated over once, and then has to be rewound (in the case of `file`, with the `seek` method, or by re-opening.)
Thomas Wouters
ahh yes of course, you are right, you would need to maintain the state of the iterator yourself within the object.
rioch