Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Revised PEP 318 - Function/Method Decorator Syntax

0 views
Skip to first unread message

Kevin Smith

unread,
Jun 10, 2003, 8:25:13 AM6/10/03
to
I have received many comments on this proposal already and have taken
them into account in this proposal. The proposed syntax is still
essentially the same, but more implementation details have been added
and the document has been restructured somewhat for clarity. There are
still some legitimate concerns about using 'as' for the delimiter as it
means something quite different in the import statement. Suggestions
for a new delimiter are welcome.


Function/Method Decorator Syntax
--------------------------------

Abstract

The current method for declaring class and static methods
is awkward and can lead to code that is difficult to understand.
This PEP introduces possible new syntax which will place the
translation of instance methods to class/static methods at
the same point in the code as the method's declaration.


Motivation

The current method of translating an instance method into a
class/static method places the actual translation at a different
point in the code than the declaration of the method. The
code below demonstrates this.

def foo(self):
perform method operation
foo = classmethod(foo)

When the method is very short, it is easy to look ahead and see
that this is a class method. However, if the method is more than
15 lines or so, the translation into a class method is not
obvious. A solution to this problem is to move the translation
of the method to the same point as the method's declaration.
The proposed syntax, shown in the example below, is discussed
in the following sections.

def foo(self) as synchronized(lock), classmethod:
perform method operation


Proposal

Probably the simplest way to place the decorator that translates
an instance method to a class/static method is illustrated in the
code below.

def classmethod foo(self):
perform method operation

The code in this example will simply perform the following.

def foo(self):
perform method operation
foo = classmethod(foo)

This syntax does not introduce any new keywords and is completely
backwards compatible with any existing code. The word between the
'def' and the actual name of the method is simply a reference to
a callable object that returns a new function reference.
This syntax could also be extended to allow multiple function
decorators in the form of a space delimited list as follows:

def protected classmethod foo(self):
perform method operation

which would be equivalent to the current form:

def foo(self):
perform method operation
foo = protected(classmethod(foo))

While this syntax is simple and easy to read, it does become
cluttered and more obscure if you wish to allow arguments to be
sent to the function decorator.

def synchronized(lock) classmethod foo(self):
perform method operation

Instead of placing the decorators in front of the function name,
a better place might be after it, as shown below. The word 'as' is
added simply as a separator to assist in readability.

def foo(self) as synchronized(lock), classmethod:
perform method operation

This syntax is quite clear and could probably be interpreted
by those not familiar with Python. The proposed syntax can be
generalized as follows:

'def' NAME '(' PARAMETERS ')' ['as' DECORATORS] ':'

where DECORATORS is a comma-separated list of expressions,
or a tuple.

Other syntaxes have been proposed in comp.lang.python. The
most common are demonstrated below.

def foo(self) [synchronized(lock), classmethod]:
perform method operation

def foo(self) (synchronized(lock), classmethod):
perform method operation

def foo(self) {'pre': synchronized(lock), 'classmethod': True}:
perform method operation

These three forms use syntax that just seems arbitrary and which
does not help the user to comprehend the meaning of it. In
addition, since the order in which the decorators are applied
may matter, the third, dictionary-style, syntax must be
eliminated.


Implementation Issues

In the following example there are two function decorators:
synchronized(lock) and classmethod.

def foo(self) as synchronized(lock), classmethod:
perform method operation

Since these all appear within the operation of the 'def'
itself, it makes sense that synchronized, lock, and
classmethod must exist at the time that the definition
is compiled. In addition, each of these arguments will be
evaluated before being applied to the compiled function.
This means that arguments like synchronized(lock) must
return a descriptor that will be applied to foo. Therefore,
the code above translates to:

def foo(self):
perform method operation
foo = classmethod(<returned-descriptor>(foo))

In the example above, <returned-descriptor> refers to the
descriptor returned by evaluating synchronized(lock).

It could easily be argued that the descriptors should be applied
in reverse order to make the application of the descriptor look
more like the resultant code. I tend to prefer this form.

def foo(self):
perform method operation
foo = <returned-descriptor>(classmethod(foo))

In either case, the modified function is bound to the function
name at compile time.


Possible Extensions

The proposed syntax is general enough that it could be used
on class definitions as well as shown below.

class foo(object) as classmodifier:
class definition here

However, there are no obvious parallels for use with other
descriptors such as property().


Conclusion

The current method of translating an instance method to a class
or static method is awkward. A new syntax for applying function
decorators should be implemented (proposed syntax shown below).

def foo(self) as synchronized(lock), classmethod:
perform method operation

The proposed syntax is simple, powerful, easy to read, and
therefore preserves those qualities of the Python language.


Copyright

This document has been placed in the public domain.


--
Kevin Smith
Kevin...@sas.com

Bernhard Herzog

unread,
Jun 10, 2003, 9:35:21 AM6/10/03
to
Kevin Smith <Kevin...@sas.com> writes:

> Implementation Issues
>
> In the following example there are two function decorators:
> synchronized(lock) and classmethod.
>
> def foo(self) as synchronized(lock), classmethod:
> perform method operation
>
> Since these all appear within the operation of the 'def'
> itself, it makes sense that synchronized, lock, and
> classmethod must exist at the time that the definition
> is compiled.

*compiled*?

Surely you mean "when the def-statement is executed."

> In addition, each of these arguments will be
> evaluated before being applied to the compiled function.
> This means that arguments like synchronized(lock) must
> return a descriptor that will be applied to foo. Therefore,
> the code above translates to:
>
> def foo(self):
> perform method operation
> foo = classmethod(<returned-descriptor>(foo))
>
> In the example above, <returned-descriptor> refers to the
> descriptor returned by evaluating synchronized(lock).

Why not simply

def foo(self):
perform method operation

foo = classmethod(synchronized(lock)(foo))

That makes it clear at which point the "synchronized(lock)" itself is
executed and that it has to return a callable object which will be
called with foo as argument without having to talk about
"returned-descriptor"s

The only thing that is not clear to me is whether it's intended that the
function is actually bound to "foo" multiple times. OTOH, I'm not sure
it would really make a difference if it were only bound once at the end.

> It could easily be argued that the descriptors should be applied
> in reverse order to make the application of the descriptor look
> more like the resultant code. I tend to prefer this form.
>
> def foo(self):
> perform method operation
> foo = <returned-descriptor>(classmethod(foo))
>
> In either case, the modified function is bound to the function
> name at compile time.

Again, I think you mean "when the def-statement is executed."

Bernhard

--
Intevation GmbH http://intevation.de/
Sketch http://sketch.sourceforge.net/
MapIt! http://www.mapit.de/

Andrew Bennetts

unread,
Jun 10, 2003, 9:49:23 AM6/10/03
to
On Tue, Jun 10, 2003 at 12:25:13PM +0000, Kevin Smith wrote:
[...]

> The proposed syntax is general enough that it could be used
> on class definitions as well as shown below.
>
> class foo(object) as classmodifier:
> class definition here
>
> However, there are no obvious parallels for use with other
> descriptors such as property().

Note that you can already do tricky stuff with properties if you really want
to define one all at once:

class EvilProperty(type):
def __new__(cls, name, bases, d):
return property(d.get('get'), d.get('set'), d.get('del'), d['__doc__'])

class C(object):
class x:
"""An evil test property"""
__metaclass__ = EvilProperty
def get(self):
print 'Getting'
return 1
def set(self, value):
print 'Setting to', value

c = C()
print c.x
c.x = 5
print C.x.__doc__

This has the advantage that you can define your property in a single block,
and you don't have to look too far ahead in the nested class to see the
crucial "__metaclass__ = EvilProperty" line.

It does kinda feel like yet-another-gratuitous-use-of-metaclasses, though ;)

-Andrew.


Moshe Zadka

unread,
Jun 10, 2003, 10:28:45 AM6/10/03
to
On Tue, 10 Jun 2003, Andrew Bennetts <andrew-p...@puzzling.org> wrote:

> class EvilProperty(type):
> def __new__(cls, name, bases, d):
> return property(d.get('get'), d.get('set'), d.get('del'), d['__doc__'])
>
> class C(object):
> class x:
> """An evil test property"""
> __metaclass__ = EvilProperty
> def get(self):
> print 'Getting'
> return 1
> def set(self, value):
> print 'Setting to', value

Wouldn't it be more readable (:)) if you did

class _EvilProperty(type):
....
class EvilProperty:
__metaclass__ = _EvilProperty

class C(object):
class x(EvilProperty):


def get(self):
print 'Getting'
return 1
def set(self, value):
print 'Setting to', value

--
Moshe Zadka -- http://moshez.org/
Buffy: I don't like you hanging out with someone that... short.
Riley: Yeah, a lot of young people nowadays are experimenting with shortness.
Agile Programming Language -- http://www.python.org/

Evan Simpson

unread,
Jun 10, 2003, 11:55:32 AM6/10/03
to
Kevin Smith wrote:
> There are still some legitimate concerns about using 'as' for the delimiter

I wouldn't worry about this. In English usage, 'as' is extremely
versatile for such a small word. My dictionary lists over a dozen
distinct meanings for it, not even counting idiomatic combinations.
Among these meanings are:

"in the capacity, character, condition, or role of"

...which seems to me to cover both renamed imports and function annotations.

Cheers,

Evan @ 4-am

Michele Simionato

unread,
Jun 10, 2003, 1:06:04 PM6/10/03
to
Andrew Bennetts <andrew-p...@puzzling.org> wrote in message news:<mailman.1055253020...@python.org>...

Carl Banks posted something similar few days ago. Evil is a good name,
since x returns a property instance and not a class. I think he wrote
something like this:

class C(object):
class x:
"""An evil test property"""

def __metaclass__(name, bases, d):


return property(d.get('get'), d.get('set'),

d.get('del'),d.get('__doc__'))


def get(self):
print 'Getting'
return 1
def set(self, value):
print 'Setting to', value

I hope nobody will ever try this in production code!


Michele

Jeremy Fincher

unread,
Jun 10, 2003, 5:02:33 PM6/10/03
to
Bernhard Herzog <b...@intevation.de> wrote in message news:<6qwufu1...@salmakis.intevation.de>...

> *compiled*?
>
> Surely you mean "when the def-statement is executed."

...

> > In either case, the modified function is bound to the function
> > name at compile time.
>
> Again, I think you mean "when the def-statement is executed."

No, he probably means "compile time." Python is compiled to bytecode
separate from being execute, the the def statement is executed at
compile time.

Jeremy

Just

unread,
Jun 10, 2003, 5:10:55 PM6/10/03
to
In article <698f09f8.03061...@posting.google.com>,
tweed...@hotmail.com (Jeremy Fincher) wrote:

Nope:

>>> for i in range(5):
... def foo(a=i):
... print a
... foo()
...
0
1
2
3
4
>>>

The code is compiled once, sure, but the function object is created at
runtime (a function object is just a thin wrapper around a code object).

Just

Chris Liechti

unread,
Jun 10, 2003, 5:43:19 PM6/10/03
to
Kevin Smith <Kevin...@sas.com> wrote in news:20030610082514442-
04...@braeburn.themorgue.org:
> Function/Method Decorator Syntax
> --------------------------------

> def foo(self) as synchronized(lock), classmethod:
> perform method operation

what's with the [] syntax discussed here a week ago?
it was something like:

def foo(self) [synchronized(lock), classmethod]:

or merging with yours:

def foo(self) as [synchronized(lock), classmethod]:

i think the use of [] is quite logic. it's a _list_ of function decorators.
and it helps readability, at least the variants should be listed as
alternatives in the PEP.

chris

--
Chris <clie...@gmx.net>

Erik Max Francis

unread,
Jun 10, 2003, 5:59:01 PM6/10/03
to
Chris Liechti wrote:

> what's with the [] syntax discussed here a week ago?
> it was something like:
>
> def foo(self) [synchronized(lock), classmethod]:

To me it says, "Hi, I'm a list!" when listness does not seem warranted.
Yes, we're talking about a sequence of properties, but the square
brackets just look wrong there to me.

> or merging with yours:
>
> def foo(self) as [synchronized(lock), classmethod]:

Doubling up the syntaxes seems unnecessary.

> i think the use of [] is quite logic. it's a _list_ of function
> decorators.
> and it helps readability, at least the variants should be listed as
> alternatives in the PEP.

It's a sequence. And, if you want to get technical, it's more likely to
be handled as a tuple internally, anyway.

--
Erik Max Francis && m...@alcyone.com && http://www.alcyone.com/max/
__ San Jose, CA, USA && 37 20 N 121 53 W && &tSftDotIotE
/ \ I'm spreading my wings and I fly / I'm still as the night
\__/ Chante Moore

Bengt Richter

unread,
Jun 10, 2003, 6:55:20 PM6/10/03
to
On 10 Jun 2003 12:25:13 GMT, Kevin Smith <Kevin...@sas.com> wrote:

>I have received many comments on this proposal already and have taken
>them into account in this proposal. The proposed syntax is still

[...]


> Instead of placing the decorators in front of the function name,
> a better place might be after it, as shown below. The word 'as' is
> added simply as a separator to assist in readability.
>
> def foo(self) as synchronized(lock), classmethod:
> perform method operation
>
> This syntax is quite clear and could probably be interpreted
> by those not familiar with Python. The proposed syntax can be
> generalized as follows:
>
> 'def' NAME '(' PARAMETERS ')' ['as' DECORATORS] ':'
>
> where DECORATORS is a comma-separated list of expressions,
> or a tuple.
>
> Other syntaxes have been proposed in comp.lang.python. The
> most common are demonstrated below.
>
> def foo(self) [synchronized(lock), classmethod]:
> perform method operation
>
> def foo(self) (synchronized(lock), classmethod):
> perform method operation
>
> def foo(self) {'pre': synchronized(lock), 'classmethod': True}:
> perform method operation
>
> These three forms use syntax that just seems arbitrary and which
> does not help the user to comprehend the meaning of it. In
> addition, since the order in which the decorators are applied
> may matter, the third, dictionary-style, syntax must be
> eliminated.

I disagree with that "must" ;-) Better to say ordering must be specified
by other means. E.g., trivially, you could add 'ordering':('pre','classmethod') to
the above dict.

IMO a list or tuple of decorator functions second-guesses the future too narrowly,
and will create an unnecessary backwards-compatibility constraint. Why not an
identifier for a class with some standard methods that are conventionally
called internally, like .decorate() ? (I'm moving forward from Skip's dict and
my previous variations on dict/dict-workalike objects ;-)

Using a class with a general __init__(*args,**kw) parameter list will permit
defining builtins for standard decorations and creating customized/derived versions
as well. The list/tuple syntax functionality could easily be implemeted with a class
by putting the elements in the call to the constructor, e.g.,

'def' NAME '(' PARAMETERS ')' ['as' DECORATORS_CLASS ['(' DECORATORS ')']] ':'

where standard DECORATORS could already be built in to a standard DECORATORS_CLASS,
or a parenthesized sequence could follow, but also allow keyword items. You could specify
DECORATORS exactly as you propose, but now the door would be open to future ideas of
decoration specification.


>
>Implementation Issues
>
> In the following example there are two function decorators:
> synchronized(lock) and classmethod.
>
> def foo(self) as synchronized(lock), classmethod:
> perform method operation

def foo(self) as DECORATORS(synchronized(lock), classmethod):
perform method operation

where DECORATORS would be a builtin standard decoration control class for your kind
of decoration sequences.

>
> Since these all appear within the operation of the 'def'
> itself, it makes sense that synchronized, lock, and
> classmethod must exist at the time that the definition
> is compiled. In addition, each of these arguments will be

ISTM this needs clarification. Is this what you mean?

<def foo source> -> [compile] -> <executable foo def>
<executable foo def> -> [execute] -> <foo bound to function> # function created by execution of def
<foo function> -> [decorate] -> <foo bound to modified function>



> evaluated before being applied to the compiled function.

You must be thinking of "compiled function" as the end result of compiling AND executing a definition?



> This means that arguments like synchronized(lock) must
> return a descriptor that will be applied to foo. Therefore,
> the code above translates to:
>
> def foo(self):
> perform method operation
> foo = classmethod(<returned-descriptor>(foo))

or


def foo(self):
perform method operation

foo = DECORATORS(synchronized(lock), classmethod).decorate(foo)

this would allow
USING_MY_LOCK = DECORATORS(synchronized(lock), classmethod)
...
def foo(self) as USING_MY_LOCK:
pass # foo body

translating to
...
def foo(self):
pass # foo body
foo = USING_MY_LOCK.decorate(foo)


>
> In the example above, <returned-descriptor> refers to the
> descriptor returned by evaluating synchronized(lock).
>

[...]


>Possible Extensions
>
> The proposed syntax is general enough that it could be used
> on class definitions as well as shown below.
>
> class foo(object) as classmodifier:
> class definition here
>
> However, there are no obvious parallels for use with other
> descriptors such as property().
>
>
>Conclusion
>
> The current method of translating an instance method to a class
> or static method is awkward. A new syntax for applying function
> decorators should be implemented (proposed syntax shown below).
>
> def foo(self) as synchronized(lock), classmethod:
> perform method operation
>
> The proposed syntax is simple, powerful, easy to read, and
> therefore preserves those qualities of the Python language.

Is the syntax a frozen mod to the def syntax, or is what comes after 'as'
an arbitrary tuple expression? E.g., would the above be spellable as

syncmeth = (synchronized(lock), classmethod)
def foo(self) as syncmeth:
perform method operation

Is it reasonable to think of this mechanism as a kind of meta-classes for functions?
Is there some unification that should not be painted out of the future?

Regards,
Bengt Richter

Ian Bicking

unread,
Jun 10, 2003, 6:03:12 PM6/10/03
to

But lists aren't used in Python syntax, only in other APIs. *Tuples*
are used, or tuple-like constructs, like:

try:
...
except (IOError, OSError):
...

There's also the question about whether a list is really what you want.
Could you do:

def syncclassmethod(lock):
return [synchronized(lock), classmethod)]

def foo(self) syncclassmethod(lock):
...

That's just bad, but disallowing that seems arbitrary. But disallowing
a (non-literal) tuple seems much more reasonable.

Ian

Skip Montanaro

unread,
Jun 10, 2003, 6:10:06 PM6/10/03
to

>> i think the use of [] is quite logic. it's a _list_ of function
>> decorators. and it helps readability, at least the variants should
>> be listed as alternatives in the PEP.

Erik> It's a sequence. And, if you want to get technical, it's more
Erik> likely to be handled as a tuple internally, anyway.

I sent this off to Kevin Smith without a cc to the list. Since some of the
recent discussion overlaps with what I sent to him I figure I might as well
let it loose here as well.

1. If people don't like 'as', what about 'mod' (as in "modified by") or
'with' (as in "augmented with")? I'm happy with nothing (as long as the
decorators are enclosed in sequence brackets of some kind), but that's
just me.

2. I still like the [ decorator, ... ] form better. In the
decorators-as-tuple case I would never list the decorators without the
surrounding parens because it's quite likely the function declaration
would spill over onto multiple lines and the open paren would allow you
to continue lines without a backslash. For consistency, I'd then include
them even if the declaration was short enough. Then I'd be left with

def foo(self) as (synchronized(lock), classmethod):
perform method operation

or worse:

def foo(self) as (classmethod,):
perform method operation

(note the required trailing comma for a singleton tuple).

The second set of parens looks vaguely like another argument list to me.
A naive interpretation of the above might be that "self" and
"classmethod" are somehow related.

List constructors don't suffer from the trailing comma disease, they are
just as helpful when lines need to be continued and they don't look as
much like another set of function parameters as tuples do.

Skip


Andrew Bennetts

unread,
Jun 10, 2003, 9:39:18 PM6/10/03
to
On Tue, Jun 10, 2003 at 02:28:45PM -0000, Moshe Zadka wrote:
> On Tue, 10 Jun 2003, Andrew Bennetts <andrew-p...@puzzling.org> wrote:
>
> > class EvilProperty(type):
> > def __new__(cls, name, bases, d):
> > return property(d.get('get'), d.get('set'), d.get('del'), d['__doc__'])

[Oops, I meant "d.get('__doc__')", not "d['__doc__']"...]

> >
> > class C(object):
> > class x:
> > """An evil test property"""
> > __metaclass__ = EvilProperty
> > def get(self):
> > print 'Getting'
> > return 1
> > def set(self, value):
> > print 'Setting to', value
>

> Wouldn't it be more readable (:)) if you did
>
> class _EvilProperty(type):
> ....
> class EvilProperty:
> __metaclass__ = _EvilProperty
>
> class C(object):
> class x(EvilProperty):
> def get(self):

[...etc...]

No, it wouldn't be more readable, because it wouldn't work...

-Andrew.


Greg Ewing (using news.cis.dfn.de)

unread,
Jun 10, 2003, 11:57:10 PM6/10/03
to
Erik Max Francis wrote:

> Chris Liechti wrote:
>> def foo(self) [synchronized(lock), classmethod]:
>
> To me it says, "Hi, I'm a list!" when listness does not seem warranted.

To me, the [] are parentheses used as one might use
parentheses to enclose an annotation in English, and
the resemblance to a list constructor is coincidental.

>> def foo(self) as [synchronized(lock), classmethod]:
>
> Doubling up the syntaxes seems unnecessary.

It also destroys the parenthetical-comment interpretation.
If both were really wanted, it would have to be

def foo(self) [as synchronized(lock), classmethod]:

--
Greg Ewing, Computer Science Dept,
University of Canterbury,
Christchurch, New Zealand
http://www.cosc.canterbury.ac.nz/~greg

Courageous

unread,
Jun 11, 2003, 12:46:50 AM6/11/03
to

>To me, the [] are parentheses used as one might use
>parentheses to enclose an annotation in English, and...

Well, how about parentheses then? These appeal to Python's
higher order semantics, where lists of things enclosed in
parentheses are (generally) immutable.

C//

Andrew Bennetts

unread,
Jun 11, 2003, 4:59:35 AM6/11/03
to

But this would:

----
class _EvilProperty(type):


def __new__(cls, name, bases, d):

if d.get('__metaclass__') == _EvilProperty:
return type.__new__(cls, name, bases, d)
else:


return property(d.get('get'), d.get('set'), d.get('del'),

d.get('__doc__'))

class EvilProperty:
__metaclass__ = _EvilProperty

class C(object):
class x(EvilProperty):
"""An evil test property"""


def get(self):
print 'Getting'
return 1
def set(self, value):
print 'Setting to', value

c = C()
print c.x
c.x = 5
print C.x.__doc__

----

:)

Ta-da! Easy, clean-looking properties, no new syntax required ;)

-Andrew.


Bernhard Herzog

unread,
Jun 11, 2003, 7:15:44 AM6/11/03
to
bo...@oz.net (Bengt Richter) writes:

> On 10 Jun 2003 12:25:13 GMT, Kevin Smith <Kevin...@sas.com> wrote:
>
> > def foo(self) {'pre': synchronized(lock), 'classmethod': True}:
> > perform method operation
> >
> > These three forms use syntax that just seems arbitrary and which
> > does not help the user to comprehend the meaning of it. In
> > addition, since the order in which the decorators are applied
> > may matter, the third, dictionary-style, syntax must be
> > eliminated.
> I disagree with that "must" ;-) Better to say ordering must be
> specified by other means. E.g., trivially, you could add
> 'ordering':('pre','classmethod') to the above dict.

The strings used in the dictionary seem to have special meaning,
especially the "classmethod" in the example above. Who defines that
meaning and how do add new strings with new meanings?

I think the dict like syntax if used for anything at all should be used
for function attributes and not decorators so that instead of

def foo(self):
pass

foo.my_attr = "bar"

I could write

def foo(self) {"my_attr": "bar"}:
pass


But then again with decorators I might just as well write

def foo(self) as attributed(my_attr = "bar"):
pass

with attributed defined as e.g.

def attributed(**kw):
def add_attrs(f):
for key, value in kw.items():
setattr(f, key, value)
return add_attrs


> Using a class with a general __init__(*args,**kw) parameter list will
> permit defining builtins for standard decorations and creating
> customized/derived versions as well. The list/tuple syntax
> functionality could easily be implemeted with a class by putting the
> elements in the call to the constructor, e.g.,
>
> 'def' NAME '(' PARAMETERS ')' ['as' DECORATORS_CLASS ['(' DECORATORS ')']] ':'
>
> where standard DECORATORS could already be built in to a standard
> DECORATORS_CLASS, or a parenthesized sequence could follow, but also
> allow keyword items. You could specify DECORATORS exactly as you
> propose, but now the door would be open to future ideas of decoration
> specification.

There's no need to add special syntax for this class based approach. The
'as' version which allows arbitrary expressions supports this already.


> > def foo(self) as synchronized(lock), classmethod:
> > perform method operation
>
> def foo(self) as DECORATORS(synchronized(lock), classmethod):
> perform method operation
>
> where DECORATORS would be a builtin standard decoration control class
> for your kind of decoration sequences.

You can easily implement DECORATORS yourself with the 'as' version as is
if you want.

Moshe Zadka

unread,
Jun 11, 2003, 7:40:50 AM6/11/03
to
On Wed, 11 Jun 2003, Gerrit Holl <ger...@nl.linux.org> wrote:

> Just a question: Why would this be evil?

Because people expect

class x(EvilProperty):
pass

to be a *class* (albeit, new style), and not a *property*.
For example, doing X() would not give you an instance of X.

Duncan Booth

unread,
Jun 11, 2003, 8:48:24 AM6/11/03
to
Bernhard Herzog <b...@intevation.de> wrote in
news:6qwufsn...@salmakis.intevation.de:

> But then again with decorators I might just as well write
>
> def foo(self) as attributed(my_attr = "bar"):
> pass
>
> with attributed defined as e.g.
>
> def attributed(**kw):
> def add_attrs(f):
> for key, value in kw.items():
> setattr(f, key, value)
> return add_attrs
>

I doubt this would have the desired effect. Perhaps we should take this as
a warning: the proposed syntax seems to lead to people writing code
snippets like the one above which would result in foo==None.

I think the problem may be that the assignment to foo is sufficiently
obfuscated that it is too easy to forget its happening, or perhaps it's
just that forcing the decorators to work by returning an intermediate
function is an extra level of indirection that it would be nice to do
without.

--
Duncan Booth dun...@rcp.co.uk
int month(char *p){return(124864/((p[0]+p[1]-p[2]&0x1f)+1)%12)["\5\x8\3"
"\6\7\xb\1\x9\xa\2\0\4"];} // Who said my code was obscure?

Andrew Bennetts

unread,
Jun 11, 2003, 8:21:12 AM6/11/03
to
On Wed, Jun 11, 2003 at 01:03:00PM +0200, Gerrit Holl wrote:
> Andrew Bennetts wrote:
>
[...]

> > class C(object):
> > class x(EvilProperty):
> > """An evil test property"""
> > def get(self):
> > print 'Getting'
> > return 1
> > def set(self, value):
> > print 'Setting to', value
[...]
>
> Just a question: Why would this be evil? I think it is explicit, simple,
> sparse, readable, practical, unambiguous... The only real anti-zen-behaviour
> is that it's nested rather than flat. But for the rest, I don't see the
> problem, what is it?

Because I'm suspicious of metaclasses in general... although this is a
pretty neat solution, and I'm tempted to use it. :)

Of course, the metaclass is behind the scenes, and the result is a simple
property, so there's no lingering weirdness. The only really nasty thing is
that I'm abusing the "class" keyword: when I say "class x(EvilProperty)", x
is not a class. *That's* what I consider evil, and why I hesitate to use
this in real code.

A more extreme example of abusing class was shown to me at PyCon by a fellow
Twisted developer (who shall remain nameless so that he won't get mobbed by
angry hordes ;) ... it looked something like this:

----
class Adder(type):
def __new__(cls, name, bases, dict):
return reduce(lambda x,y: x+y, bases, 0)

class C(1, 2, 3, 4, 5):
__metaclass__ = Adder

print C
----

There's some things the class keyword was just never meant to do.

-Andrew.


Bernhard Herzog

unread,
Jun 11, 2003, 9:07:41 AM6/11/03
to
Duncan Booth <dun...@NOSPAMrcp.co.uk> writes:

> Bernhard Herzog <b...@intevation.de> wrote in
> news:6qwufsn...@salmakis.intevation.de:
>
> > But then again with decorators I might just as well write
> >
> > def foo(self) as attributed(my_attr = "bar"):
> > pass
> >
> > with attributed defined as e.g.
> >
> > def attributed(**kw):
> > def add_attrs(f):
> > for key, value in kw.items():
> > setattr(f, key, value)
> > return add_attrs
> >
>
> I doubt this would have the desired effect. Perhaps we should take this as
> a warning: the proposed syntax seems to lead to people writing code
> snippets like the one above which would result in foo==None.

Oops, indeed. The nested add_attrs should return f.

> I think the problem may be that the assignment to foo is sufficiently
> obfuscated that it is too easy to forget its happening, or perhaps it's
> just that forcing the decorators to work by returning an intermediate
> function is an extra level of indirection that it would be nice to do
> without.

I think it's rare that one would actually write decorator functions and
if you do you'll hopefully start with a test cases which will
immediately spot the problem if foo ends up being None. For those who
don't write test cases it should become obvious (well, at least it will
raise an exception) once foo is being called for the first time.

Note also that it's only easy to get wrong if you need to pass arguments
to the decorator function and therefore need one extra level of
indirection.

It seems to me that writing decorator functions is on a more abstract
level than what most Python programmers will do in their day to day
work. In this respect the decorators are similar to meta classes but not
as abstract.

Bernhar

Michele Simionato

unread,
Jun 11, 2003, 10:58:35 AM6/11/03
to
bo...@oz.net (Bengt Richter) wrote in message news:<bc5nko$4c9$0...@216.39.172.122>...> syncmeth = (synchronized(lock), classmethod)

> def foo(self) as syncmeth:
> perform method operation
>
> Is it reasonable to think of this mechanism as a kind of meta-classes for functions?
> Is there some unification that should not be painted out of the future?
>
> Regards,
> Bengt Richter

Looks like an interesting idea. Do you have any further thought on that?

Michele

Gerrit Holl

unread,
Jun 11, 2003, 11:25:01 AM6/11/03
to
Andrew Bennetts wrote:
> On Wed, Jun 11, 2003 at 01:03:00PM +0200, Gerrit Holl wrote:
> > Andrew Bennetts wrote:
> > > class C(object):
> > > class x(EvilProperty):
> > > """An evil test property"""
> > > def get(self):
> > > print 'Getting'
> > > return 1
> > > def set(self, value):
> > > print 'Setting to', value
> >
> > Just a question: Why would this be evil? I think it is explicit, simple,
> > sparse, readable, practical, unambiguous... The only real anti-zen-behaviour
> > is that it's nested rather than flat. But for the rest, I don't see the
> > problem, what is it?

> Of course, the metaclass is behind the scenes, and the result is a simple


> property, so there's no lingering weirdness. The only really nasty thing is
> that I'm abusing the "class" keyword: when I say "class x(EvilProperty)", x
> is not a class. *That's* what I consider evil, and why I hesitate to use
> this in real code.

Ah, I see. But it is tempting... :)

Makes me wonder: what is the definition of a (new-style) class?
Is that anything that is an instance of type?

> A more extreme example of abusing class was shown to me at PyCon by a fellow
> Twisted developer (who shall remain nameless so that he won't get mobbed by
> angry hordes ;) ... it looked something like this:
>
> ----
> class Adder(type):
> def __new__(cls, name, bases, dict):
> return reduce(lambda x,y: x+y, bases, 0)
>
> class C(1, 2, 3, 4, 5):
> __metaclass__ = Adder
>
> print C

Hmmm...... this is kinda cool, somehow... instead of an "obfuscated code"
contest, this would be a contender for an "abusing code" contest.

Cool contest that would be :)

yours,
Gerrit.

--
122. If any one give another silver, gold, or anything else to keep, he
shall show everything to some witness, draw up a contract, and then hand
it over for safe keeping.
-- 1780 BC, Hammurabi, Code of Law
--
Asperger Syndroom - een persoonlijke benadering:
http://people.nl.linux.org/~gerrit/
Het zijn tijden om je zelf met politiek te bemoeien:
http://www.sp.nl/

Michele Simionato

unread,
Jun 11, 2003, 12:51:53 PM6/11/03
to
Andrew Bennetts <andrew-p...@puzzling.org> wrote in message news:<mailman.105533414...@python.org>...

I would *never* use in production code Andrew's suggestion (I realize
he also would not use it). But just to defend metaclasses, I want
to say that this kind of Evil Things have nothing to do with them:

class C(object):
def __new__(cls,x):
return x

c=C(1)
print type(c) #=><type 'int'> !

The problem is with __new__, the metaclass issue is a red herring.
I would not change the current behaviour of __new__, I found at least
a convenient use case for this, but of course I am not advocating this
kind of abuses.

Michele

Michele Simionato

unread,
Jun 11, 2003, 1:26:36 PM6/11/03
to
Kevin Smith <Kevin...@sas.com> wrote in message news:<20030610082...@braeburn.themorgue.org>...>
> This syntax could also be extended to allow multiple function
> decorators in the form of a space delimited list as follows:
>
> def protected classmethod foo(self):
> perform method operation
>
> which would be equivalent to the current form:
>
> def foo(self):
> perform method operation
> foo = protected(classmethod(foo))
>

I have just realized that there is an issue here.
IMHO, there is only one reasonable way of defining a "decorator": it
should be defined as a descriptor class, that takes a function and returns
a descriptor object. classmethods and staticmethods work this way and
"protected" should be the same. Now, if 'protected' is such a decorator,
it would only accepts *functions* as input, not classmethod objects.

Therefore

def protected classmethod foo(self):
perform method operation

or
def foo(self)[protected,classmethod]:
perform method operation

or whatever would give an error if interpreted as

foo = protected(classmethod(foo))

since protected would expect a function, not a classmethod object.

I think 'def foo(self)[protected,classmethod]' should be interpreted as

foo=type('protectedclassmethod',(protected,classmethod),{})(foo)

and protected.__init__ should be cooperative with classmethod.__init__.

IOW, the only thing that makes sense here is multiple inheritance,
not composition: descriptors are classes, not functions!

Michele

Ian Bicking

unread,
Jun 11, 2003, 4:16:55 PM6/11/03
to
On Wed, 2003-06-11 at 07:48, Duncan Booth wrote:
> > def foo(self) as attributed(my_attr = "bar"):
> > pass
> >
> > with attributed defined as e.g.
> >
> > def attributed(**kw):
> > def add_attrs(f):
> > for key, value in kw.items():
> > setattr(f, key, value)
> > return add_attrs
> >
>
> I doubt this would have the desired effect. Perhaps we should take this as
> a warning: the proposed syntax seems to lead to people writing code
> snippets like the one above which would result in foo==None.

Accidental Nones happens a lot in Python, I don't think we should try to
avoid it. But, it might be nice to say that the decorator is not
allowed to return None, so there would be an exception in this case...
then at least these bugs will be quickly caught.

Ian

Chris Liechti

unread,
Jun 11, 2003, 6:18:40 PM6/11/03
to
Erik Max Francis <m...@alcyone.com> wrote in
news:3EE654A5...@alcyone.com:

> Chris Liechti wrote:
>
>> what's with the [] syntax discussed here a week ago?
>> it was something like:
>>
>> def foo(self) [synchronized(lock), classmethod]:
>
> To me it says, "Hi, I'm a list!" when listness does not seem warranted.
> Yes, we're talking about a sequence of properties, but the square
> brackets just look wrong there to me.

ok, so you dont like any sort of indexing neither ;-)
as in >>> 'hello'[3]

>> or merging with yours:
>>
>> def foo(self) as [synchronized(lock), classmethod]:
>
> Doubling up the syntaxes seems unnecessary.
>
>> i think the use of [] is quite logic. it's a _list_ of function
>> decorators.
>> and it helps readability, at least the variants should be listed as
>> alternatives in the PEP.
>
> It's a sequence. And, if you want to get technical, it's more likely to
> be handled as a tuple internally, anyway.

in that case it should be an iterator. "for" does nowdays work with
iterators and not sequences anymore, right? ;-)

in fact, we could take
def name(args) as <iterator>:
in cosideration. where "<iterator>" can be anything like in "for"
"for x in 1,2,3:", "for x in (1,):", "for x in [1,2,3]:"
"for x in range(3)", etc...
and that would in turn bring the mergend syntax back as "for x in 1:" is
not allowed neither. you have to use a list or a tuple (or an other
iterator)

what would be the advantage of allowing an iterator?
well, i'd say either you allow exactly one decorator, or a
list/sequence/iterator.
in both cases everything is possible, just that the "one only" solution
would require a decorator function that in turn takes a list of decoradors
if you need more than one at once.
as with exacly one decorator:
----
def decorator(iterable):
def decorate(function):
for d in iterable:
function = d(function)
return function
return decorate

def f() as decorator([d2, d3]):
pass
---
or what i would prefer:
---
def f() as [d2, d3]:
pass
---
when an iterator is allowed. i think almost all ppl agree that it should be
possible to specify more than one decorator at a time. i dont see an
argument for restricting it to a special new syntax if there is a keyword
between the "def" and the decorators. i'd use the same rules as for the
"for" loop.

things are different without the keyword. "def f(x) [d1, d2]:" here i would
require "[]" and nothing else. "()" is not good in case of only one
element, as Skip already pointed out.


btw. "as" is no keyword in the usual sense, you can bind a value to that
name... (does not shadow "import x as y")
>>> as = 1

so maybe it should be "in" to avoid new keywords...
"def f(x) in [d1, d2]:"
-> define function f with argument x in the list of decorators d1, d2
may not be 100% correct english, but it's a programming laguange anyway ;-)

chris

--
Chris <clie...@gmx.net>

Erik Max Francis

unread,
Jun 11, 2003, 6:32:05 PM6/11/03
to
Chris Liechti wrote:

> Erik Max Francis <m...@alcyone.com> wrote in
> news:3EE654A5...@alcyone.com:
>

> > To me it says, "Hi, I'm a list!" when listness does not seem
> > warranted.
> > Yes, we're talking about a sequence of properties, but the square
> > brackets just look wrong there to me.
>
> ok, so you dont like any sort of indexing neither ;-)
> as in >>> 'hello'[3]

Well, I _did_ say "there." There isn't any indexing going on there,
either, so no, I still don't think the square bracket list delimiters
belong.

--
Erik Max Francis && m...@alcyone.com && http://www.alcyone.com/max/
__ San Jose, CA, USA && 37 20 N 121 53 W && &tSftDotIotE

/ \ If I had another face, do you think I'd wear this one?
\__/ Abraham Lincoln

Steven Taschuk

unread,
Jun 11, 2003, 9:01:13 PM6/11/03
to
Quoth Michele Simionato:
[...]

> IMHO, there is only one reasonable way of defining a "decorator": it
> should be defined as a descriptor class, that takes a function and returns
> a descriptor object. classmethods and staticmethods work this way and
> "protected" should be the same. Now, if 'protected' is such a decorator,
> it would only accepts *functions* as input, not classmethod objects.

Functions are descriptors too, though (since they have __get__ for
the unbound/bound method machinery). What if we thought of these
things as descriptor decorators instead of function decorators?
Then transitivity is no problem. (Of course, they'd expect that
the descriptor they wrap would have a __get__ which provides a
callable.)

def pre(prefunc):
class prewrapper(object):
def __init__(self, descriptor):
self.descriptor = descriptor
def __get__(self, obj, type_=None):
func = self.descriptor.__get__(obj, type_) # <--
def prewrapped(*args, **kwargs):
prefunc(*args, **kwargs)
return func(*args, **kwargs)
prewrapped.__doc__ = func.__doc__
return prewrapped
return prewrapper

For the specific intended use, it wouldn't matter that other
callables (types, instances of classes with __call__ ...) are not
necessarily also descriptors.

(The implementation above does have the wart that the prefunc
doesn't see the self argument.)

--
Steven Taschuk stas...@telusplanet.net
"I'm always serious, never more so than when I'm being flippant."
-- _Look to Windward_, Iain M. Banks

Greg Ewing (using news.cis.dfn.de)

unread,
Jun 12, 2003, 2:24:58 AM6/12/03
to
Courageous wrote:
> Well, how about parentheses then?

Somehow it looks right to me with square brackets.
They say "this is something special". I think I
would find it visually confusing having two
adjacent round-bracketed lists.

Michael Hudson

unread,
Jun 12, 2003, 9:05:33 AM6/12/03
to
Andrew Bennetts <andrew-p...@puzzling.org> writes:

> A more extreme example of abusing class was shown to me at PyCon by a fellow
> Twisted developer (who shall remain nameless so that he won't get mobbed by
> angry hordes ;) ... it looked something like this:
>
> ----
> class Adder(type):
> def __new__(cls, name, bases, dict):
> return reduce(lambda x,y: x+y, bases, 0)
>
> class C(1, 2, 3, 4, 5):
> __metaclass__ = Adder
>
> print C
> ----
>
> There's some things the class keyword was just never meant to do.

I managed do something like this:

class Add(GF, int, str):
def call(i, s):
print "int, str"

class Add(GF, int, int):
def call(i, i):
print "int, int"

then

>>> Add(1,2)
int, int
>>> Add(1,"a")
int, str

Can't find a version of the code that actually works now, though.

Cheers,
M.

--
It *is*, however, rather juvenile, in contravention of the
Trades Descriptions Act, and will lead eventually to the Dragon
of Unhappiness flying up your bottom. -- Peter Ellis, ucam.chat

Michele Simionato

unread,
Jun 12, 2003, 1:01:17 PM6/12/03
to
Steven Taschuk <stas...@telusplanet.net> wrote in message news:<mailman.1055379262...@python.org>...

> Quoth Michele Simionato:
> [...]
> > IMHO, there is only one reasonable way of defining a "decorator": it
> > should be defined as a descriptor class, that takes a function and returns
> > a descriptor object. classmethods and staticmethods work this way and
> > "protected" should be the same. Now, if 'protected' is such a decorator,
> > it would only accepts *functions* as input, not classmethod objects.
>
> Functions are descriptors too, though (since they have __get__ for
> the unbound/bound method machinery).

This reminds me that the biggest difference between custom descriptors
and functions is that we cannot subclass the built-in FunctionType at
the present.
I remember Tim Peters saying that this is possible in principle and was
not done because of lack of serious interests for the moment being.

> What if we thought of these
> things as descriptor decorators instead of function decorators?
> Then transitivity is no problem. (Of course, they'd expect that
> the descriptor they wrap would have a __get__ which provides a
> callable.)
>
> def pre(prefunc):
> class prewrapper(object):
> def __init__(self, descriptor):
> self.descriptor = descriptor
> def __get__(self, obj, type_=None):
> func = self.descriptor.__get__(obj, type_) # <--
> def prewrapped(*args, **kwargs):
> prefunc(*args, **kwargs)
> return func(*args, **kwargs)
> prewrapped.__doc__ = func.__doc__
> return prewrapped
> return prewrapper
>
> For the specific intended use, it wouldn't matter that other
> callables (types, instances of classes with __call__ ...) are not
> necessarily also descriptors.
>
> (The implementation above does have the wart that the prefunc
> doesn't see the self argument.)

I see your point. Still, multiple inheritance would seem more consistent
to me, and also would be more natural when extending the notation to classes
i.e.

class C[Traced,Protected]: pass

would have a metaclass inherited from Traced and Protected.

M.

Bengt Richter

unread,
Jun 12, 2003, 2:49:00 PM6/12/03
to

Nothing too deep. It just strikes me that functions are objects too, and there are
variations like closures and generators and bound methods etc, yet the process of
creating these things is not exposed for customization in a unified way. I'm not sure
what would be in the dct if cls,name,bases,dct were passed to a meta-something on
completion of the function definition, analogous to metaclass at the completion of
class definition, but I assume the code object would be there and tinkerable at
the __new__ stage in a way that might not be possible later. E.g., being able
to add pre-bound local variables to a function might be interesting (like default
arg values, but not in the arg list).

Maybe with module new and some clever hacks a user function class could be worked up
for concept testing. Lots of handwaving here ;-)

Regards,
Bengt Richter

Steven Taschuk

unread,
Jun 12, 2003, 7:18:00 PM6/12/03
to
Quoth Michele Simionato:
> Steven Taschuk <stas...@telusplanet.net> wrote:
[...]

> > What if we thought of these
> > things as descriptor decorators instead of function decorators?
[...]

> I see your point. Still, multiple inheritance would seem more consistent
> to me, [...]

... achieving the chaining with a cooperative __get__? You're
right, that seems quite natural.

> [...] and also would be more natural when extending the notation to classes


> i.e.
>
> class C[Traced,Protected]: pass
>
> would have a metaclass inherited from Traced and Protected.

I'd be against such a syntax, fwiw. Too easy to mistake for the
normal subclass syntax. (Wouldn't be a big deal if one were
trained to notice the difference by frequent use of metaclasses;
but few users would be.)

--
Steven Taschuk "[W]e must be very careful when we give advice
stas...@telusplanet.net to younger people: sometimes they follow it!"
-- "The Humble Programmer", Edsger Dijkstra

Greg Ewing (using news.cis.dfn.de)

unread,
Jun 12, 2003, 10:40:42 PM6/12/03
to
Steven Taschuk wrote:
> Quoth Michele Simionato:
>
> > class C[Traced,Protected]: pass

>
> I'd be against such a syntax, fwiw. Too easy to mistake for the
> normal subclass syntax.

Maybe it would help if the base class parentheses
were required in that case:

class C()[Traced,Protected]:

although I think I would tend to write it with
a space between:

class C() [Traced,Protected]:

Michele Simionato

unread,
Jun 13, 2003, 8:19:01 AM6/13/03
to
"Greg Ewing (using news.cis.dfn.de)" <g2h5d...@sneakemail.com> wrote in message news:<bcbc8b$hf4ub$3...@ID-169208.news.dfncis.de>...

> Steven Taschuk wrote:
> > Quoth Michele Simionato:
> >
> > > class C[Traced,Protected]: pass
> >
> > I'd be against such a syntax, fwiw. Too easy to mistake for the
> > normal subclass syntax.
>
> Maybe it would help if the base class parentheses
> were required in that case:
>
> class C()[Traced,Protected]:
>
> although I think I would tend to write it with
> a space between:
>
> class C() [Traced,Protected]:

It was a typo: I meant

class C(object)[Traced,Protected]: pass

(in my mind the new syntax should only work for new style classes).
I am not a strong supporter of the square bracket syntax, BTW. I
would accept other alternatives, such as

class C(object) is Traced,Protected # but "is" is already used

or even better

class C(object) becomes Traced,Protected # requires a new keyword

Anyway, to me the precise syntax is somewhat a minor point; I don't
like "as" but all the rest would work.


Michele

A. Lloyd Flanagan

unread,
Jun 13, 2003, 12:23:53 PM6/13/03
to
"Greg Ewing (using news.cis.dfn.de)" <g2h5d...@sneakemail.com> wrote in message news:<bcbc8b$hf4ub$3...@ID-169208.news.dfncis.de>...
...

>
> Maybe it would help if the base class parentheses
> were required in that case:
>
> class C()[Traced,Protected]:

I like that.

I actually consider it a wart that I can't say:
class x():
def __init__(self):
print "hi"

In fact I'd be just as happy to see the option to omit the parentheses
go away altogether.

Skip Montanaro

unread,
Jun 13, 2003, 12:35:30 PM6/13/03
to

Lloyd> In fact I'd be just as happy to see the option to omit the
Lloyd> parentheses go away altogether.

Alas, it's not currently an option.

Skip

0 new messages