Are decorators really that different from metaclasses...

Bengt Richter bokr at
Tue Aug 31 23:36:09 CEST 2004

On Tue, 31 Aug 2004 09:25:43 -0400, Paul Morrow <pm_mon at> wrote:

>Paul Morrow wrote:
>> But why speculate.  Let's see if this is really going to be unclear in 
>> practice.  Ask someone who has never seen Python before which are the 
>> local variables in the following def.
>>    def circumference(diameter):
>>       """Calculate the diameter of a circle."""
>>       __author__ = 'Paul Morrow'
>>       __version__ = '0.1'
>>       pi = 3.14
>>       return pi * diameter
>And while you're at it, ask them which lines get executed at function 
>definition time, and which get executed when the function is called.
IMO you will have better luck promoting your functionality if you make
your proposed magic syntactically distinct using something other than underscores,
which are just name characters in any legal name that matches [a-zA-Z_][a-zA-Z0-9_]*

I think your magic becomes more interesting if we separate the two issues of
referring to attributes of the current object and doing it at def-time as opposed
to normal execution time (i.e., call-time for function body code), and get some
orthogonal general primitives going ;-)

To make things simple, let's say '$' means the current object, and let's say that
we use '..' in various ways to indicate execution or evaluation at def-time. E.g.,
prefixed to an assignment '=' it would mean do the binding (and evaluation of the
whole right hand side) at def-time. Thus your example becomes

      def circumference(diameter):
         """Calculate the diameter of a circle."""
         $.author ..= 'Paul Morrow'
         $.version ..= '0.1'
         pi = 3.14
         return pi * diameter

But notice that the assignment of pi is a wasteful repeated action for every call.
If instead if we wrote (additionally illustrating constant folding effect):
         pi ..= 3.14    # or __import__('math').pi for better accuracy ;-)
         mask7 ..= 2**7-1 # net binding as if mask7=127

the effect would be a def-time pre-binding of pi and mask7, visible locally as if
      def circumference(diameter, pi=3.14, mask7=2**7-1):
         # etc
but without using the default arg hack. Of course, a default local arg name binding is
refreshed to the original rhs at every call, so even if you set pi=0.0 it will appear
as 3.14 next time. Unlike if you used $.pi ..= 3.14 and then set $.pi=0.0 -- that would
be preset once and rebindings would persist. $.xxx would also provide good opportunity
for optimizing access, and the $ attribute name space would be a handy substitute for
a mutable closure object named something other than '$'.

BTW, another interesting thing would be if the function class were modified so that it would
recognize descriptors when attributes of its instances. This would make assignments of
properties sticky unless they had a self-deleting method.
         def foo(): $.prop ..= property(lambda self: 'hi from %s.prop'%self.__name__)

would mean foo.prop would return 'hi from foo.prop' and foo.prop = 123 would raise an
exception. Or the function class could be made subclassable.

BTW, an expression should also be able to have a def-time-evaluated term, without the rest
being def-time, e.g.,
    def area(r): return ..(__import__('math').pi)*r*r

Note also what you could do with a mutable when you don't need the default arg hack:

    def pow10(x):
        cache ..={}
        try: return cache[x]
        except KeyError: return cache.setdefault(x, 10**x)

In classes, if there is a classvar ..= something, the resulting code
for the class definition should theoretically not have the assignment
as byte codes when the class body executes, but the binding should be
visible as-if. I don't know if there's an efficient way to do that.

Seems like it would have to be pre-bound in the class dict (that gets
passed to a metaclass if there is one), but I guess a fresh copy would
be needed. So that would wind up way more expensive than just a plain
class-def-time assignment. Hm, probably easiest just not to allow it.

Inside methods, of course, ..=  would be fine. Method def-times are when
the class body executes (unless they are later added, in which case the
..= applies wherever the function/method was defined and its def executed.

Too many ideas in one post?
Ok, enough lunch fun.

Bengt Richter

More information about the Python-list mailing list