PEP 288 ponderings

Michael Sparks zathras at thwackety.com
Sun Jan 2 20:13:04 EST 2005


On Sun, 2 Jan 2005, Ian Bicking wrote:

> Steven Bethard wrote:
> > PEP 288 was mentioned in one of the lambda threads and so I ended up
> > reading it for the first time recently.  I definitely don't like the
> > idea of a magical __self__ variable that isn't declared anywhere.  It
> > also seemed to me like generator attributes don't really solve the
> > problem very cleanly.  An example from the PEP[1]:
> >
> >     def mygen():
> >         while True:
> >             print __self__.data
> >             yield None
> >
> >     g = mygen()
> >     g.data = 1
> >     g.next()                # prints 1
> >     g.data = 2
> >     g.next()                # prints 2
>
> I don't get why this isn't good enough:
>
>      def mygen(data):
>          while True:
>              print data[0]
>              yield None

It's OK, but rather limited. IMHO a nicer approach is to use decorators to
decorate a generator with extra attributes. Specifically to embed the
generator inside an iterator inside an anonymous class. (We're exploring
this as a alternative approach to the system we currently use at work)
(Hmm... Now I describe it that sounds rather hideous, but the result is
nice)

First of all the decorator:

import copy
def wrapgenerator(bases=object, **attrs):
   def decorate(func):
       class statefulgenerator(bases):
          __doc__ = func.__doc__
          def __init__(self,*args):
             super(statefulgenerator, self).__init__(*args)
             self.func=func(self,*args)
             for k in attrs.keys(): self.__dict__[k] = copy.deepcopy(attrs[k])
             self.next=self.__iter__().next
          def __iter__(self): return iter(self.func)
       return statefulgenerator
   return decorate

This would be used thus:

@wrapgenerator(component)
def forwarder(self):
   # The generator, note the explicit self for local state
   # for this generator
   "Simple data forwarding generator"
   while 1:
      if self.dataReady("inbox"):
         self.send("outbox",self.recv("inbox"))
      elif self.dataReady("control"):
         if self.recv("control") == "shutdown":
            break
      yield 1
   self.send("signal","shutdown")
   yield 0

This clearly assumes a set of attributes, local methods etc.

In this case our local methods, attribute etc are from this class:
class component(object):
   def __init__(self, *args):
       # Default queues
       self.queues = {"inbox":[],"control":[],"outbox":[],"signal":[]}
   def send(self, box, object): self.queues[box].append(object)
   def dataReady(self,box): return len(self.queues[box])>0
   def recv(self, box): # NB. Exceptions aren't caught
      X=self.queues[box][0]
      del self.queues[box][0]
      return X

This then gets used in the usual way:
>>> f = forwarder()
>>> f
<__main__.statefulgenerator object at 0x402dcccc>
>>> f.send("inbox", "some data")
>>> f.send("inbox", "some more data")
>>> f.next()
1
>>> f.next()
1
>>> f.recv("outbox")
'some data'
>>> f.recv("outbox")
'some more data'

If you "just" want (arbitrary) generator attributes, then that's a lot
easier (since we don't need to support an arbitrary base class):

import copy
def add_attrs_to_generator(**attrs):
   def decorate(func):
       class statefulgenerator:
          __doc__ = func.__doc__
          def __init__(self,*args):
             self.func=func(self,*args)
             for k in attrs.keys(): self.__dict__[k] = copy.deepcopy(attrs[k])
             self.next=self.__iter__().next
          def __iter__(self): return iter(self.func)
       return statefulgenerator
   return decorate

@add_attrs_to_generator()
def simpleGen(self):
   while True:
      print dir(self)
      yield None

We can also add default attributes:

@add_attrs_to_generator(test=1,simple=2)
def simpleGen(self):
   while True:
      print dir(self)
      yield None

>>> g=simpleGen()
>>> g.next()
['__doc__', '__init__', '__iter__', '__module__', 'func', 'next',
'simple', 'test']

We've been using this general approach of giving generators local state
for communications for a while now, and it's been rather interesting/fun
to use. However we've been using explicit declaration of a class, and then
a standard fixed name for the generator (main) instead. (we've been
thinking about using decorators as syntactic sugar)

Being able to pass exception in would be very nice, however we've used a
"control"  attribute, queue in practice, to allow for need to be able to
shutdown an arbitrary generator. However, I don't think the __self__ part
of the PEP is explicitly required - since you can build that onto
generators using python quite happily now (@add_attrs_to_generator).

The code for the stuff we're playing with (in case anyone's interested :)
is at http://kamaelia.sourceforge.net/. The specific subsystem that uses
this approach has been released as a separate download (Axon[1]) on the
sourceforge project page[2], and the main system (obviously) uses this
heavily and can be browsed via viewcvs. [3]
   [1] http://kamaelia.sourceforge.net/Docs/Axon.html
   [2] http://sourceforge.net/projects/kamaelia
   [3] http://cvs.sourceforge.net/viewcvs.py/kamaelia/Code/Python/Kamaelia/Kamaelia/

Best Regards,


Michael.




More information about the Python-list mailing list