[Python-Dev] PEP 318 bake-off?

Phillip J. Eby pje at telecommunity.com
Thu Apr 1 18:18:47 EST 2004


At 11:08 AM 4/1/04 -0800, Guido van Rossum wrote:
> > >What I'm asking (especially of Phillip) is to collect a set of
> > >realistic method declarations using decorators; we can then
> > >collectively format these using any of the possible syntaxes, and see
> > >how they look.
> >
> > I'd be happy to scrounge up some samples from existing code using
> > 'property' and 'classmethod' as well as some of PEAK's decorators, and I
> > definitely think that Jack Diedrich and Bob Ippolito's samples should be
> > included as well.
> >
> > Important question, though: do we include code bodies, or just use 'pass'
> > for the bodies?  If we include the body, how much of the body?  Should we
> > include entire classes, especially if the class itself needs a decorator,
> > and multiple methods have decorators?
>
>Why not provide the bodies, for added realism?

Okay, here's one using two decorators, in today's syntax.  It's excerpted 
from an I/O scheduling component in 'peak.events'.  The component manages a 
set of read/write/error file handles, and allows pseudothreads waiting on 
those handles to resume when I/O is possible.  The 'select()' operation is 
itself performed by a pseudothread called 'monitor'.  Each instance of the 
component should have exactly one such pseudothread, which should begin 
running as soon as the component is "assembled" (attached to an application).

Two decorators control this: 'events.taskFactory', which accepts a 
generator function and returns a function that returns a new pseudothread 
each time it's invoked.  That is, it's roughly equivalent to:

def taskFactory(func):
     return lambda *args,**kw: Task(func(*args,**kw))

except that there is some extra magic so that introspecting the returned 
function still shows the same argument signature.  (Which is important for 
documentation tools like pydoc and epydoc).

The second decorator is 'binding.Make', which takes a 1-argument callable 
and returns a descriptor that will invoke the callable only once: when the 
attribute is first accessed for a given instance.  The result of the 
callable is cached in the object's instance dictionary, where it will be 
retrieved on any subsequent access.

So, applying the two decorators (i.e. [events.taskFactory, binding.Make]) 
to a 1-argument function results in an attribute that will be automatically 
initialized when first used.  By applying an extra keyword argument to 
'binding.Make' in the current implementation, we can tell the descriptor to 
automatically initialize itself when the componet is assembled.  (Note: 
this is not the same as __init__ time; a PEAK component is considered 
"assembled" when it is made the child of a component that knows its "root" 
component, and thus can be certain of its entire configuration environment.)

So, I would probably render this example with these decorators:

[events.taskFactory, binding.Make(uponAssembly=True)]

in order to specify that the function is a generator that should be run as 
a task (pseudothread), it should be run at most once, and it should exist 
as soon as the component knows its configuration environment.  (Note: the 
'uponAssembly' bit works today only with old-style 
'foo=binding.Make(foo,...)' syntax, not decorator syntax.)  Anyway, here's 
the example:




     def monitor(self) [events.taskFactory, binding.Make(uponAssembly=True)]:

         r,w,e = self.rwe
         count = self.count
         sleep = self.scheduler.sleep()
         time_available = self.scheduler.time_available
         select = self.select
         error = self._error

         while True:
             yield count; resume()   # wait until there are selectables
             yield sleep; resume()   # ensure we are in top-level loop

             delay = time_available()
             if delay is None:
                 delay = self.checkInterval

             try:
                 rwe = self.select(r.keys(),w.keys(),e.keys(),delay)
             except error, v:
                 if v.args[0]==EINTR:
                     continue    # signal received during select, try again
                 else:
                     raise

             for fired,events in zip(rwe,self.rwe):
                 for stream in fired:
                     events[stream]().send(True)


>(I still think class decorators are a separate case, and much weaker
>-- you can do this by having a single 'decoratable' metaclass and
>setting __decorators__ = [...] in the class body.)

Or you can use the "class advisors" mechanism I implemented for PyProtocols 
and Zope 3, which is clean and convenient in today's syntax.  Its only 
pitfall is that you absolutely *must* specify __metaclass__ first if you 
are specifying one, or your class advisors won't work right.  Actually, the 
other pitfall for anybody taking this approach is that a correct 
implementation of class advisors is *hard*, because it depends on a correct 
re-implementation of much of Python's metaclass validation logic.  But, at 
least there is one working implementation available to steal from.  :)



>I think that SPARK syntax and everything else that people have
>traditionally added to docstring markup that isn't strictly speaking
>documentation (even some extreme cases of doctest usage) ought to be
>considered as candidates for attribute-ification.

David Goodger mentioned docutils, so I mocked up a couple of 
'rst_directive' examples in a seperate message.




More information about the Python-Dev mailing list