[Python-Dev] Re: PEP 318: Decorators last before colon

Robert Brewer fumanchu at amor.org
Wed Apr 7 01:50:00 EDT 2004


[Guido van Rossum]
> > ...while far from perfect, in
> > terms of readability setting function attributes in a 
> decorator is so
> > much better than setting them after the function has been defined,
> > that I believe we have no choice but to provide it.

[Me]
> The more I read statements like this, the more I think of replacing:
> 
> def foo(arg1, arg2):
>     pass
> foo = classmethod(foo)
> foo.attrA = 3
> 
> ...with code like:
> 
> foo = classmethod()
> foo.attrA = 3
> def foo(arg1, arg2):
>     pass
> 
> Why would such additional features not be standard attributes of
> function objects?
>8 
> All in all, I'd prefer that function decorators and attributes use
> simple, known techniques like assignment and callables (as 
> shown above),
> than become monolithic, crystallized all-in-one statements like:
> 
> def foo(arg1 as str, arg2 as int) returns str [classmethod] 
> {attrA: 3}:
> 
> I understand the desire to keep all those bits near each other on the
> page, but stronger is my desire to make them independent 
> statements. If
> we could bind a "blank" function object and supply its actual 
> codeblock
> in a later statement, the rest falls magically into place, IMO.

I sat down and banged out a metaclass solution, just to see if I *could*
get the syntax I wanted in the example Shoe class, and to see what might
be required behind the scenes. I put Shoe and all the decorator junk in
one module to make the syntax look as clean as if Decorable, etc. were
builtins.

Basically, Decorable (the metaclass) looks at all attributes of the Shoe
class; if there's an attribute "x" and an attribute "x_suite", then
Shoe.x gets rebound as x(x_suite). The example provides classmethod and
arbitrary function attributes. There are ways to clean it up, of course;
this was a quick and dirty proof-of-concept. Rather than playing with
"_suite", it should probably either reuse the same name "x" or use a
token other than def. I also arbitrarily chose to allow a tuple or list
for the decorator (but didn't demonstrate it).


----decorate.py----

import new

class Decorable(type):
    def __init__(cls, name, bases, dct):
        type.__init__(cls, name, bases, dct)
        for attr in dct:
            if attr + "_suite" in dct:
                # Wrap the attr_suite in the attr decorator.
                inner_func = getattr(cls, attr + "_suite")
                decorator = getattr(cls, attr)
                if isinstance(decorator, (tuple, list)):
                    wrapped_func = inner_func
                    for item in decorator:
                        wrapped_func = item.decorate(wrapped_func)
                else:
                    wrapped_func = decorator.decorate(inner_func)
                setattr(cls, attr, wrapped_func)
                
                # [Optional] Unbind the inner function from the class.
                delattr(cls, attr + "_suite")


class Decorator(object):
    def __init__(self):
        self.attributes = {}
    
    def __setattr__(self, key, value):
        if key == 'attributes':
            self.__dict__['attributes'] = value
        else:
            self.__dict__['attributes'][key] = value
    
    def decorate(self, function):
        for key, value in self.attributes.iteritems():
            function.__setattr__(key, value)
        return function


class clsmethod(Decorator):
    def decorate(self, function):
        newfunc = new.instancemethod(function, function.im_class(),
function.im_class())
        return super(clsmethod, self).decorate(newfunc)


class Shoe(object):
    __metaclass__ = Decorable
    
    sized_shoe = clsmethod()
    sized_shoe.author = "Robert E Brewer"
    def sized_shoe_suite(cls, size):
        """Make a new Shoe with a 'size' attribute."""
        newShoe = Shoe()
        newShoe.size = size
        return newShoe

----end decorate.py----


>>> import decorate
>>> dir(decorate.Shoe)
['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__',
'__hash__', '__init__', '__metaclass__', '__module__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__',
'__weakref__', 'sized_shoe']

>>> decorate.Shoe.sized_shoe.author
'Robert E Brewer'

>>> s = decorate.Shoe.sized_shoe(3)
>>> s
<decorate.Shoe object at 0x0115CAD0>
>>> s.size
3


Robert Brewer
MIS
Amor Ministries
fumanchu at amor.org



More information about the Python-Dev mailing list