[Python-Dev] block-based decorators and other block issues

Martin Zarate mzarate at uoguelph.ca
Fri Aug 13 14:20:22 CEST 2004


Our discussion has taken an interesting turn, leading to this concept 
of "super-decorators" that are more versatile than normal decorators, because 
they can accept multiple objects, and because they can play with namespaces.

First of all, it is important not to get too far ahead of ourselves - 
decorators should not be full "custom blocks" like having a "threadsafe" 
decorator block and a "fork function" block wherein the decoratotor is 
changing the fundamental behaviour of a block of code.  Concepts that we see 
in Ruby (I highly, highly recommend that people interested in this subject 
read over the Ruby tutorial, it is very insightful on the concept of versatile 
block constructs) could be brought to Python, but it would have to be done 
properly through a good general-purpose mechanism.

Decorators are simply a filtering mechanism - they take an object defined with 
the decorator, and then either a) return a substitute to the parent scope or, 
possibly b) throw it out.  They are not blocking concepts.

The reason is simple - speed.  A decorator shouldn't have to perform 
fasttolocals and vice versa.  It doesn't need to be its own variable 
namespace - it can sit within the external namespace.  To do otherwise is 
suboptimal.  The decorator should not have its own scope.  Instead, it should 
provide a simple filter.

Now, it would be nice if Python had another versatile block structure usable 
for custom stuff that does involve having its own full namespace (hell, just 
a "block" keyword would do) but I'll get to that later.

First, I need a keyword.   I will use "turn", just to keep things different, 
but anything will do - has has been remarked elsewhere, a naked tuple with 
a ":" suffix would be enough - it would differentiate the conversion block 
from standard comma-delimited lists, while not looking like any other block.

At any rate, people advocating the "decorator block" concept that I suggested  
keep assuming that the descriptor functions are functions that take a whole 
namespace in and put a whole other namespace out, which is then imported into 
the parent namespace - horribly inefficient.  Use that later for custom blocks.

The call signature of a descriptor could simply be

descriptor(object, name, holder, doc, args)

where object is the object being described, name is the string with its name 
(immutable, as always) and holder is the current contents of the holder.  The 
return value of the descriptor function is assigned into the holder at the end 
of the operation - if no change is desired and the nested objects are to be 
thrown out, then simply return the holder parameter.  The name parameter 
exists for the sake of naming objects.  With the holder parameter, inspection 
of the existing object is allowed, so that the "property_get" descriptor 
(which actually alters an existing property or creates a new one if none 
exists) can work.  The reason for the longer function signature is so that 
more complicated syntax is available to the decorator function - such as a 
docstring that can be prepended to the nested docstrings.  This seems silly 
for functions, but would be appropriate when decorating non-function objects 
(such as data members).

Creating a new scope block for decorators would be too slow, but still would 
be very useful for more bizarre custom linguistic constructs.  A general 
purpose "scope" command with access to external and internal namespaces would 
be extremely useful.

Imagine the general purpose "scope" statement:

scope handler1(args), handler2(args), handler3(args):

where the contained code within the block would be run, then its local 
namespace harvested, as well as the external namespace, both passed in to

handlerN(inner, outer, args)

this would pretty much let you do whatever you want within a random scope: 
block, allowing for some bizzarro macroesque stuff that would make descriptors 
look simple by comparison.  Personally I think such a structure would be silly 
and dangerous, but its what many coders sound like they want.

A better, more general-purpose structure would be an "object" block like

object name handler(args):

wherein the contained code is run on the spot and the local namespace is 
converted into a dictionary, which is then called with the signature

handler(namespace, name, args)

where namespace is the namespace is the local namespace, name is the string of 
the name, and args is the argument tuple.  You could use this to code your own 
analogs to the class structure of Python.  Say you want a forced single-
inheritence-tree class system?  You could have one with such custom blocks, or 
any other alternate-class-structure.  Hell, with such a system the class 
itself wouldn't need to be a true keyword as it is now, but instead could 
behave as simply just one more "object" expression handler - as I understand 
it, all it does is take that namespace and the argument tuple and do some fun 
stuff with it and then plonk that info (plus the name of the class) into 
the "type" constructor.  That doesn't need to be interpreter level.

So instead of 

class foo(bar baz):

we get

object foo class(bar baz):

which could be substituted with any wierd thing you want instead of "class" - 
like a quick module object creator with none of the overhead of a class, for 
example.  Or you could do the much coveted "instance" block that many people 
have clamored for, in which you subclass an instance instead of subclassing a 
class.  Or lets say you're making a game engine and you need to make a 
separate class system for engine objects (the situation I was in, actually)and 
you want to be able to work with them as if they were classes, but also want 
it to inherit some invisible engine settings not apparent to the python 
interpreter from other engine objects, as well as instead of making some kinds 
of functions visible to the interpreter they're loaded into the event handler 
automatically (this is personal experience here - I wanted these features very 
badly).

Or you could just make a quick dict maker

def dictblock(namespace, name, args):
    return namespace;

Like sort of the reverse of a "with" block.

Take note that this system would be by far the best way to handle the property 
object, rather than mucking about with descriptors.

Works as such...

object foo propertyblock:
    def fget(self):
         return "bar"
    def fset(self):
         pass
    def fdel(self):
         del self.foo
    doc = "baz"

(alternately, docstrings could be implemented like functions, filling the 
__doc__ entry of the dictionary)

and implemented like...

propertyblock(space, name, args):
    return property(space["fget"], space["fset"], space["fdel"], space["doc"])

You could do a nice shortcut involving 
passing the namespace dict into the keywords dict while calling "property", 
but that's the more legible way.

The point is that this would be distinct from the descriptor block concept, as 
descriptors don't need to play with the namespaces beyond one object and one 
name at a time.

But none of this explains why I like using a block for descriptors - I'm just 
trying to explain to those who thought of the possibilites of a block where 
you could play with the local and surrounding namespace how yes, that stuff 
would be useful, but would have a lot more overhead than simply a descriptor 
block.

There are places a multi-line descriptor block is ugly, but there are also 
places where it is goregeous.  Public and Private are good examples that C++ 
devs would love, rather than saying public and private for each function its

turn private:
    foo = 1
    bar = 2
    baz = 3

Hell, if you really don't like the keyword, we could get even wierder and play 
with a new syntax: use the @ with a bracket and nest the block within Guido's 
syntax...

@[static,
private,
functor]:
    def foo:
         pass

Wow, I covered way, way too much ground with that post.  Seriously people - 
read the Ruby tutorial "Programming Ruby" http://www.rubycentral.com/book/ to 
see the gorgeous way that Ruby handles the whole concept of blocks.  
Unfortunately the rest of the language is thoroughly illegible, but the block 
concept is cool.



More information about the Python-Dev mailing list