decorators and 2.4

So here's the state of play with decorators and 2.4. Guido is undecided on the syntax - he writes "I'm seriously considering doing it Java-style", but adds that he is totally swamped for the next two weeks. He finishes with: "Feel free to suggest this as a project for an adventurous python-dev'er though." So, who's feeling adventurous? I'm convinced that this should go into 2.4 if possible, and I don't think there's any _technical_ risks (as far as implementation goes), the only problem is the syntax (and yes, that's a technical problem too, but you know what I mean). Channelling Guido, via his EP keynote (http://www.python.org/doc/essays/ppt/euro2004/euro2004.ppt) I'm assuming that "Java-style" is something like: @staticmethod def blah(args): body @funcattrs(vegetable="blah", author="GvR") def blah2(args): body It's not clear to me how you'd specify multiple decorators this way, perhaps Guido can give more details... So, let the floodgates open. Remember, we _can_ change this any time up until 2.4b1, if there's a decision that the chosen form sucks. :-) -- Anthony Baxter <anthony@interlink.com.au> It's never too late to have a happy childhood.

Guido is undecided on the syntax - he writes "I'm seriously considering doing it Java-style", but adds that he is totally swamped for the next two weeks. He finishes with: "Feel free to suggest this as a project for an adventurous python-dev'er though."
So, who's feeling adventurous? I'm convinced that this should go into 2.4 if possible, and I don't think there's any _technical_ risks (as far as implementation goes), the only problem is the syntax (and yes, that's a technical problem too, but you know what I mean).
Channelling Guido, via his EP keynote (http://www.python.org/doc/essays/ppt/euro2004/euro2004.ppt) I'm assuming that "Java-style" is something like:
@staticmethod def blah(args): body
@funcattrs(vegetable="blah", author="GvR") def blah2(args): body
Right on both counts.
It's not clear to me how you'd specify multiple decorators this way, perhaps Guido can give more details...
Easy: @staticmethod @funcattrs(vegetable="blag", author="GvR") def blah2(args): body I would love to see an implementation of this idea. One advantage mentioned by Fredrik Lundh of this, and also of my other favorite, over "decorators-after-args" is that the decorators are easily cut-and-pasted -- it's much easier to cut whole lines than sections of a line, and cutting a multiple-lines-spanning section is even worse. If people prefer my favorite (decorator-list-before-def, C# style) over Java-style, that's fine too... :-) --Guido van Rossum (home page: http://www.python.org/~guido/)

Guido writes:
One advantage mentioned by Fredrik Lundh of this, and also of my other favorite, over "decorators-after-args" is that the decorators are easily cut-and-pasted -- it's much easier to cut whole lines than sections of a line, and cutting a multiple-lines-spanning section is even worse.
Yeah, it makes the code marginally easier to edit, but it also makes the code harder to read. Used to be that "def" and "class" would stand out in the file, being at the left edge. Now you've got these new lines, with, God help us, new syntax, cluttering up the left edge. What's even worse, these decorators come before the function, so you encounter them before you even know what function you're talking about. A reading nightmare. I still prefer "def FUNCTION (ARGS) as DECORATOR[S]:", myself. Bill

Bill Janssen wrote:
...
Yeah, it makes the code marginally easier to edit, but it also makes the code harder to read. Used to be that "def" and "class" would stand out in the file, being at the left edge. Now you've got these new lines, with, God help us, new syntax, cluttering up the left edge.
The left edge is already cluttered. a = 5 b = a() def foo(xyz): abc = 5 c = 20 It is the indentation that alerts your eye to the function definition.
What's even worse, these decorators come before the function, so you encounter them before you even know what function you're talking about. A reading nightmare.
Nobody reads code one line at a time from the top down. You scan for the bit that interests you and then look around for its context. Paul Prescod

Paul Prescod writes:
The left edge is already cluttered.
a = 5 b = a() def foo(xyz): abc = 5 c = 20
It is the indentation that alerts your eye to the function definition.
Paul, most modules have global variables defined only in one place, usually well-marked, so you don't usually get the clutter that you cite here. Scripts, of course, are an exception.
Nobody reads code one line at a time from the top down. You scan for the bit that interests you and then look around for its context.
It's of course the scanning that I'm talking about. Having only "def" and "class" at the left edge makes it remarkably easy to scan for a function or class definition. Putting oddly-shaped decorators there too ruins this. But I disagree with your statement here. Everyone reads *unfamiliar* code one line at a time from the top. If you're really reading it, I mean, rather than just looking for something in it. Bill

Bill Janssen wrote:
It's of course the scanning that I'm talking about. Having only "def" and "class" at the left edge makes it remarkably easy to scan for a function or class definition. Putting oddly-shaped decorators there too ruins this.
I have to admit that I see this as unnecessary alarmism and I don't think it is supported by fact. Looking at just a few standard library modules you will see that it is common to put documentation (i.e. metadata) and globals in the left-margin. I'll demonstrate in two code examples and we'll see if they are really much different in terms of readability. In my opinion, the basic code shape (outdent, indent) is the same and that is what matters for readability. # Dispatch routine for best timer program (fastest if # an integer but float works too that). def trace_dispatch_i(self, frame, event, arg): timer = self.timer t = timer() - self.t - self.bias if self.dispatch[event](self, frame,t): self.t = timer() else: self.t = timer() - t # put back unrecorded delta # Dispatch routine for macintosh (timer returns time in # 1/60th second) def trace_dispatch_mac(self, frame, event, arg): timer = self.timer t = timer()/60.0 - self.t - self.bias if self.dispatch[event](self, frame, t): self.t = timer()/60.0 else: self.t = timer()/60.0 - t # put back Versus: # Dispatch routine for best timer program (fastest if # an integer but float works too that). @classmethod def trace_dispatch_i(self, frame, event, arg): timer = self.timer t = timer() - self.t - self.bias if self.dispatch[event](self, frame,t): self.t = timer() else: self.t = timer() - t # put back unrecorded delta # Dispatch routine for macintosh (timer returns time in # 1/60th second) @something(something) def trace_dispatch_mac(self, frame, event, arg): timer = self.timer t = timer()/60.0 - self.t - self.bias if self.dispatch[event](self, frame, t): self.t = timer()/60.0 else: self.t = timer()/60.0 - t # put back Paul Prescod

Paul, I think you're proving my point. There's an interesting paper on www.liveink.com where they claim that one of the problems with classically formatted text is that the lines above and below the line that's currently being read make it harder to focus on that line. To me, the same seems true in your examples of the @foo decoration syntax. Bill

On Fri, Jun 25, 2004, Bill Janssen wrote:
Paul Prescod writes:
Nobody reads code one line at a time from the top down. You scan for the bit that interests you and then look around for its context.
But I disagree with your statement here. Everyone reads *unfamiliar* code one line at a time from the top. If you're really reading it, I mean, rather than just looking for something in it.
Well, I'm not "everyone", though I suppose you could argue that I'm just looking for something. When I look at a module, I first skim it to find the interesting parts. I almost always know what the module is supposed to do, so I'm looking for the bits that support the module's primary purpose. You've got a point about cluttering the code, but *all* the proposed solutions clutter code in some way. I agree with Paul that you're being unnecessarily alarmist. My ordering of preference is: Decorator after ``def`` JavaDoc style I'm -1 on the proposal to put decorator before ``def``. -- Aahz (aahz@pythoncraft.com) <*> http://www.pythoncraft.com/ "Typing is cheap. Thinking is expensive." --Roy Smith, c.l.py

The left edge is already cluttered.
a = 5 b = a() def foo(xyz): abc = 5 c = 20
It is the indentation that alerts your eye to the function definition.
But the indentation alone doesn't distinguish a def from an if or while or any other indented thing. Most people would leave some space before and after the function definition: a = 5 b = a() def foo(xyz): abc = 5 c = 20 Unfortunately, leaving space between the decorators and the function would break the visual association between them. Greg Ewing, Computer Science Dept, +--------------------------------------+ University of Canterbury, | A citizen of NewZealandCorp, a | Christchurch, New Zealand | wholly-owned subsidiary of USA Inc. | greg@cosc.canterbury.ac.nz +--------------------------------------+

Guido:
@staticmethod @funcattrs(vegetable="blag", author="GvR") def blah2(args): body
If people prefer my favorite (decorator-list-before-def, C# style) over Java-style, that's fine too... :-)
I don't really like either of them, but to my eyes your original one is definitely the least worst. I find the @-signs extremely ugly. Greg Ewing, Computer Science Dept, +--------------------------------------+ University of Canterbury, | A citizen of NewZealandCorp, a | Christchurch, New Zealand | wholly-owned subsidiary of USA Inc. | greg@cosc.canterbury.ac.nz +--------------------------------------+

In article <200406250357.i5P3vRNh020599@cosc353.cosc.canterbury.ac.nz>, Greg Ewing <greg@cosc.canterbury.ac.nz> wrote:
If people prefer my favorite (decorator-list-before-def, C# style) over Java-style, that's fine too... :-)
I don't really like either of them, but to my eyes your original one is definitely the least worst. I find the @-signs extremely ugly.
I wouldn't say they're pretty, but I like them better than the alternative C# list-followed-by-def-magically-becomes-decorator. And the argument about being able to cut and paste them a line at a time makes sense to me too. -- David Eppstein http://www.ics.uci.edu/~eppstein/ Univ. of California, Irvine, School of Information & Computer Science

On Wed, 2004-06-23 at 05:36, Guido van Rossum wrote:
@staticmethod @funcattrs(vegetable="blag", author="GvR") def blah2(args): body
I would love to see an implementation of this idea.
OK, here's my stab at it: python.org/sf/979728 It's fairly minimal - I guess the biggest impact change is that funcdef is now defined in Grammar/Grammer as: decorator: '@' test [NEWLINE] decorators: decorator+ funcdef: [decorators] 'def' NAME parameters ':' suite As this version of funcdef has an optional element at the beginning, I changed the funcdef child node references (in compile.c and parsermodule.c) to use a new macro RCHILD() which takes a negative index and treats it as relative to the end of the node's children array. Mark Russell

Anthony Baxter <anthony@interlink.com.au> writes:
Channelling Guido, via his EP keynote (http://www.python.org/doc/essays/ppt/euro2004/euro2004.ppt) I'm assuming that "Java-style" is something like:
@staticmethod def blah(args): body
@funcattrs(vegetable="blah", author="GvR") def blah2(args): body
It's not clear to me how you'd specify multiple decorators this way, perhaps Guido can give more details...
So, let the floodgates open. Remember, we _can_ change this any time up until 2.4b1, if there's a decision that the chosen form sucks. :-)
I'd love to discuss this, but channelling Brett channelling Guido,
I think Guido has said no more syntax ideas on this one. It's either before the 'def', after the argument list, or like Java 1.5 .
Are you really interested in more syntax ideas, Guido? -- Dave Abrahams Boost Consulting http://www.boost-consulting.com

Anthony Baxter <anthony@interlink.com.au> writes:
So, let the floodgates open. Remember, we _can_ change this any time up until 2.4b1, if there's a decision that the chosen form sucks. :-)
Here's thinking in a different direction altogether: No special syntax Instead, expose enough functionality in standard library functions that an appropriately-written pure-python "decorate" function can do it. decorate(staticmethod, my_decorator) def f(x): whatever() Since function definitions are executable statements, it should in principle be possible to arrange something that allows one to hook the execution of that statement. Perhaps it's already doable with the debugger hook? If you really want special syntax, this would at least be a way to gain experience with decorators before introducing syntax changes, and there's no reason you couldn't keep the decorate function alive ongoingly. It could also allow more flexible semantics (e.g. decorate everything in the current scope with blah until I say otherwise). You can go on introduce various syntactic abominations using the special operator methods without actually changing the language syntax, too. I happen to like things like this, but I can understand that some won't. For example: decorated[staticmethod, my_decorator] def f(x): whatever() decorators <= staticmethod, my_decorator def f(x): whatever() etc... -- Dave Abrahams Boost Consulting http://www.boost-consulting.com

David Abrahams <dave@boost-consulting.com>:
You can go on introduce various syntactic abominations using the special operator methods without actually changing the language syntax, too.
...
+staticmethod +foodstyle(sandwich) def eject(tomato): ... Greg Ewing, Computer Science Dept, +--------------------------------------+ University of Canterbury, | A citizen of NewZealandCorp, a | Christchurch, New Zealand | wholly-owned subsidiary of USA Inc. | greg@cosc.canterbury.ac.nz +--------------------------------------+

Greg Ewing <greg@cosc.canterbury.ac.nz> writes:
David Abrahams <dave@boost-consulting.com>:
You can go on introduce various syntactic abominations using the special operator methods without actually changing the language syntax, too.
...
+staticmethod +foodstyle(sandwich) def eject(tomato): ...
Big smile over here! -- Dave Abrahams Boost Consulting http://www.boost-consulting.com

At 01:26 PM 6/24/04 -0400, David Abrahams wrote:
Anthony Baxter <anthony@interlink.com.au> writes:
So, let the floodgates open. Remember, we _can_ change this any time up until 2.4b1, if there's a decision that the chosen form sucks. :-)
Here's thinking in a different direction altogether:
No special syntax
Instead, expose enough functionality in standard library functions that an appropriately-written pure-python "decorate" function can do it.
decorate(staticmethod, my_decorator) def f(x): whatever()
Since function definitions are executable statements, it should in principle be possible to arrange something that allows one to hook the execution of that statement. Perhaps it's already doable with the debugger hook?
Hmmm. You probably *could* create such a function in CPython, maybe even as far back as 2.1 (since all it needs is sys._getframe and the tracing hook), but it wouldn't be portable to Jython. And, boy, would it be a sick and twisted piece of code. You'd need to do something like keep a copy of the locals and on each "new line" event from the trace hook, you'd need to both call the old trace hook (so that debugging could still take place) and check to see if the locals had a new function object. As soon as the function object appeared, you could do your dirty work (after first restoring the *old* value bound to that name, so the decorators would have access to the previous binding of the name). About the only bit you couldn't do in pure Python would be decorating a function defined in a nested scope, because "fast locals" aren't accessible from pure Python. But for module-level functions and methods in classes, this might actually work. Heck, I'm rather tempted to try to write it and see if it does. At worst, it might be a fun way to learn the ins and outs of sys.settrace(). [goes away for 30 minutes or so] Hm, interesting. After a bit of twiddling, I've got a rough draft of the trace mechanism. It seems to be possible to chain tracer functions in the manner described, and I got it to work even with pdb running. That is, if the debugger was already tracing a frame, adding co-operative trace functions didn't appear to disturb the debugger's operation. However, there are certainly some "interesting" peculiarities in the trace function behavior. There seems to be a bit of voodoo necessary. Specifically, it seems as though one must call 'sys.settrace()' *and* set 'frame.f_trace' in order to change a local frame trace function, even though in theory just returning a new trace function from a 'line' event should work. Ah well. Here's a proof-of-concept version, that uses right-to-left application and simple calling for the decorators, but as you'll see, the bulk of the code is devoted to mechanism rather than policy. You'll see that it's easy to write different policies, although the way I have the trace mechanism set up, later tracers execute *before* earlier tracers. I'm not sure if this is the right way to go, but it seems to read best with this syntax. Anyway, it works with CPython 2.2. Caveat: do *not* use listcomps or any other assignment statements between your 'decorate()' invocation and the function definition, or the decorators will be applied to the bound-to variable! Personally, I think the 'decorate()' function is probably actually not that useful, compared to making specialized decoration functions like, say: classmethod_follows() def foo(klass, x, y, z): # ... Only with better names. :) Also, using this approach means you can do something like: [classmethod_follows()] def foo(klass, x, y, z): # ... which comes awfully close to Guido's preferred syntax! Anyway, concept code follows, and I'm quite tempted to add this to PyProtocols, which already has a similar function for class decoration. # ======= import sys def add_assignment_advisor(callback,depth=2): frame = sys._getframe(depth) oldtrace = [frame.f_trace] old_locals = frame.f_locals.copy() def tracer(frm,event,arg): if event=='call': if oldtrace[0]: return oldtrace[0](frm,event,arg) else: return None try: if frm is frame and event !='exception': for k,v in frm.f_locals.items(): if k not in old_locals: del frm.f_locals[k] break elif old_locals[k] is not v: frm.f_locals[k] = old_locals[k] break else: return tracer callback(frm,k,v) finally: if oldtrace[0]: oldtrace[0] = oldtrace[0](frm,event,arg) frm.f_trace = oldtrace[0] sys.settrace(oldtrace[0]) return oldtrace[0] frame.f_trace = tracer sys.settrace(tracer) def decorate(*decorators): if len(decorators)>1: decorators = list(decorators) decorators.reverse() def callback(frame,k,v): for d in decorators: v = d(v) frame.f_locals[k] = v add_assignment_advisor(callback) decorate(classmethod) def f(klass,x,y): pass print f

Hello Phillip, On Fri, Jun 25, 2004 at 01:27:03AM -0400, Phillip J. Eby wrote:
About the only bit you couldn't do in pure Python would be decorating a function defined in a nested scope, because "fast locals" aren't accessible from pure Python.
Would you believe it? Your implementation works just fine in this case too :-) sysmodule.c invokes PyFrame_FastToLocals and PyFrame_LocalsToFast around a call to a trace function, so that any change to f_locals from the trace function are actually correctly reflected into the frame. Excellent. Let's put this in the standard library and close the issue ;-) As an easy extension, decorate(attr=value, ...) could just assign attributes to the function object. Also, the fact that it works with other assignment statements is a bonus in my opinion, e.g.: class X: decorate(doc("Number of instances created.")) counter = 0 def __init__(self): X.counter += 1 However, it gets a bit lost if several variables are assigned to in one line. A bientot, Armin.

At 11:01 AM 6/25/04 +0100, Armin Rigo wrote:
Hello Phillip,
On Fri, Jun 25, 2004 at 01:27:03AM -0400, Phillip J. Eby wrote:
About the only bit you couldn't do in pure Python would be decorating a function defined in a nested scope, because "fast locals" aren't accessible from pure Python.
Would you believe it? Your implementation works just fine in this case too :-) sysmodule.c invokes PyFrame_FastToLocals and PyFrame_LocalsToFast around a call to a trace function, so that any change to f_locals from the trace function are actually correctly reflected into the frame.
Wow. You're right, it does work. The time machine strikes again. :)
Excellent. Let's put this in the standard library and close the issue ;-)
Ah, if only that *would* close the issue. It really just opens more, I'm afraid.
As an easy extension, decorate(attr=value, ...) could just assign attributes to the function object. Also, the fact that it works with other assignment statements is a bonus in my opinion, e.g.:
See, I told you it opens more issues. Among them: * The location in the stdlib of 'decorate' * The signature of 'decorate', including whether it should even be *called* 'decorate', or something else. * Whether to have a 'decorate' function at all, or whether it's better to just list specialized decorator objects/functions (I generally favor the latter, since 'decorate' is IMO a dead chicken that hasn't appeared in any widely-supported syntax proposals to date). * Which of the numerous possible syntax variations should be used/recommended: +classmethod +attrs(spam="fidget") def foo(...): ... [classmethod(), attrs(spam="fidget")] def foo(...): ... ~classmethod ~attrs(spam="fidget") def foo(...): ... -classmethod -attrs(spam="fidget") def foo(...): ... All of these are possible, and each likely to have *some* proponents. Oh, and let's not forget the question of application order of the decorations. :) I personally favor a(b(c(...))) ordering for decorations that are listed before the 'def', even though I favor a(), b(), c() ordering for decorations that come after. Anyway, I think it's probably more realistic to say that the presence of a viable implementation approach that allows numerous policies and syntax variants to be experimented with, even in existing Python versions, means it's more likely that Guido will *not* allow this in the stdlib for 2.4, and leave it for the frameworks that need this to fight it out amongst themselves. I'm not sure if this is good or bad. Personally, I'd prefer Guido to "bless" one of the above syntax alternatives as the recommended way to do these things, along with a "blessed" order in which decorators should be applied. Then, in any of my frameworks that I implement this for, I'll be able to be "forward compatible" with other frameworks that do the same. By the way, I should also mention that I think with this approach, my strongest objection to Guido's original syntax proposal goes away: if you have to *call* the decorators, it makes it more obvious that something is happening. When you could just list '[classmethod]', it's not obvious that that line *does* anything. If you have '[classmethod()]', it's apparent that something is being invoked.

On Friday 25 June 2004 04:09 pm, Phillip J. Eby wrote:
* Whether to have a 'decorate' function at all, or whether it's better to just list specialized decorator objects/functions (I generally favor the latter, since 'decorate' is IMO a dead chicken that hasn't appeared in any widely-supported syntax proposals to date).
Ooohh... time for some fun! Some proposals have included "as". Since "as" isn't really a keyword, how about: from decorate import decorate as as as(classmethod) def myAlternateConstructor(...): # make something interesting: return 42 Too bad this is ugly for the simple attribute decorator: as(spam="fidget") def foo(...): send_fred_more_spam_please() -Fred -- Fred L. Drake, Jr. <fdrake at acm.org> PythonLabs at Zope Corporation

As I see it, any decorator that appears on lines previous to a function impairs code readability by making the function harder to find. When editing code with the standard 4-space tab, the function name is very conveniently lined up with the code below it, and in most code I've seen, there's a blank line above it, like this: def something(foo): do_something() def somethingelse(bar): do_something_else() Now say we were to add decorators to this. Then visually, to find the function, you'd be searching through a non-predetermined set of decorators first, as opposed to searching for a solid piece of whitespace then scrolling your eyes one line down, a fixed position from the left edge of the editor. The function name would be embedded in noise, making the code more confusing to edit. This particular issue can be remedied in two ways. First off, a list of decorators with brackets, as Phillip's solution makes possible, but with some limitation to prevent multiple lines of them (or at least a prescribed coding convention prohibiting it without true enforcement). It would still take away from readability a little bit, but not in such a bad way. The brackets actually set off the decorators nicely. Something like: [decorators] def func(args): pass The other solution I see is something like what Bill said which is: def func (args) as [decorators]: The idea of bracketing them as a list is, I think, a very good one, and it would of course still have to be determined whether decorators are functions, whether they should be called, etc. I kind of like the idea of them being functions that are called on the function, and if they need additional arguments, make them higher order functions such that the decorator list element is called with func as its argument. Just my 2c, Brian On Fri, 25 Jun 2004 16:18:38 -0400, Fred L. Drake, Jr. <fdrake@acm.org> wrote:
On Friday 25 June 2004 04:09 pm, Phillip J. Eby wrote:
* Whether to have a 'decorate' function at all, or whether it's better to just list specialized decorator objects/functions (I generally favor the latter, since 'decorate' is IMO a dead chicken that hasn't appeared in any widely-supported syntax proposals to date).
Ooohh... time for some fun! Some proposals have included "as". Since "as" isn't really a keyword, how about:
from decorate import decorate as as
as(classmethod) def myAlternateConstructor(...): # make something interesting: return 42
Too bad this is ugly for the simple attribute decorator:
as(spam="fidget") def foo(...): send_fred_more_spam_please()
-Fred
-- Fred L. Drake, Jr. <fdrake at acm.org> PythonLabs at Zope Corporation
_______________________________________________ Python-Dev mailing list Python-Dev@python.org http://mail.python.org/mailman/listinfo/python-dev Unsubscribe: http://mail.python.org/mailman/options/python-dev/zorander%40gmail.com
-- The years ahead pick up their dark bags. They move closer. There's a slight rise in the silence then nothing.

On Jun 25, 2004, at 3:52 PM, Brian L. wrote:
such a bad way. The brackets actually set off the decorators nicely. Something like:
[decorators] def func(args): pass
+1 IMHO, making decorators functions is a bad idea. Decorators are metadata about the function to which they refer. Using the same syntax for decorators as for functions conceptually weakens this distinction and encourages misuse of decorators, even to the point of potentially encouraging (or implying) side-effects as a result of decoration. IMHO, jb

On Jun 25, 2004, at 5:51 PM, Jeff Bone wrote:
On Jun 25, 2004, at 3:52 PM, Brian L. wrote:
such a bad way. The brackets actually set off the decorators nicely. Something like:
[decorators] def func(args): pass
+1
IMHO, making decorators functions is a bad idea. Decorators are metadata about the function to which they refer. Using the same syntax for decorators as for functions conceptually weakens this distinction and encourages misuse of decorators, even to the point of potentially encouraging (or implying) side-effects as a result of decoration.
Uh, the WHOLE POINT we want this is to have side-effects. If it doesn't make the function act in a different way, it might as well live in the doc string or something. The most wanted use cases are all function transformations, not the setting attributes on function objects. -bob

Uh, the WHOLE POINT we want this is to have side-effects. If it doesn't make the function act in a different way, it might as well live in the doc string or something.
Declarative vs. imperative. Design-time *definitional* modification of behavior vs. runtime. I don't really think you want side-effects in the literal sense. E.g. "classmethod" isn't a side-effect, it's a definitional thing. If you really want side-effecting operations on functions, you've already got that given higher-order / first-class functions. jb

At 09:23 AM 6/26/04 -0500, Jeff Bone wrote:
Uh, the WHOLE POINT we want this is to have side-effects. If it doesn't make the function act in a different way, it might as well live in the doc string or something.
Declarative vs. imperative. Design-time *definitional* modification of behavior vs. runtime. I don't really think you want side-effects in the literal sense.
Actually, he very well might, and a number of other people definitely do. One use case repeatedly mentioned on Python-Dev has been to register functions or classes with frameworks, therefore definitely involving a literal side-effect (i.e. modification to an unrelated data structure). (I wonder if I should submit a patch for PEP 318 to add this to the motivation section, because it seems a lot of people keep repeating this "no side-effects metadata" misconception.)
E.g. "classmethod" isn't a side-effect, it's a definitional thing. If you really want side-effecting operations on functions, you've already got that given higher-order / first-class functions.
Please read the current "Motivation" section of PEP 318. The point isn't that such operations aren't possible, it's that they're not very readable in standard Python syntax. In contrast, most other languages with first-class functions allow function definitions to be used as *expressions*, which means that they can be nested within a decoration expression, giving the reader a context for understanding the function definition. PEP 318 seeks to remedy this clarity/readability issue, by providing an alternative syntax for expressions that will "wrap" a function definition that follows.

On Jun 26, 2004, at 10:27 AM, Phillip J. Eby wrote:
Actually, he very well might, and a number of other people definitely do. One use case repeatedly mentioned on Python-Dev has been to register functions or classes with frameworks, therefore definitely involving a literal side-effect (i.e. modification to an unrelated data structure).
IMHO, the fact that this results in a modification to a data structure in framework code is an implementation detail; conceptually it can be argued that this is *still not a side-effect* --- you are in essence *saying something about* the function or object in question, not expressing something algorithmic. My fear is that we end up seeing stuff like this: [SomeDecorator(someFreeVariable=Assignment)] def foo(...): x = someFreeVariable ... My biggest concern in all this is that we end up muddying up readability and clarity by trying to improve it.
(I wonder if I should submit a patch for PEP 318 to add this to the motivation section, because it seems a lot of people keep repeating this "no side-effects metadata" misconception.)
I think that before we "clarify" the PEP that the intent here *is* to allow side-effects we should get some clarity on what a side-effect *is.* Clearly there's enough lack of consensus about this; perhaps we need to see more examples of how various folks would use it. jb

Jeff Bone <jbone@deepfile.com> writes:
Uh, the WHOLE POINT we want this is to have side-effects. If it doesn't make the function act in a different way, it might as well live in the doc string or something.
Declarative vs. imperative. Design-time *definitional* modification of behavior vs. runtime. I don't really think you want side-effects in the literal sense. E.g. "classmethod" isn't a side-effect, it's a definitional thing. If you really want side-effecting operations on functions, you've already got that given higher-order / first-class functions.
I'm a big fan of declarative programs. That said, declarativeness (especially in Python) is more a matter of "notational flavor" than anything we can measure. It's certainly independent of whether there are actual side-effects. Also, I'm suspicious of any heroic efforts to prevent language features from being "abused". Some of the most powerful techniques in many languages were not envisioned by the language designers. The languages I like (by their nature) tend to encourage the invention of these unforseen techniques. Python is one of them. -- Dave Abrahams Boost Consulting http://www.boost-consulting.com

On Jun 28, 2004, at 12:27 PM, David Abrahams wrote:
I'm a big fan of declarative programs.
BTW, for the record --- I'm not under any illusions about Python's "declarativeness." ;-)
That said, declarativeness (especially in Python) is more a matter of "notational flavor" than anything we can measure. It's certainly independent of whether there are actual side-effects.
Also, I'm suspicious of any heroic efforts to prevent language features from being "abused".
Let me be specific about what I'm actually worried about, here. Specifically: I've been playing a bit with some type-inferencing stuff and optional typing. One desiderata for decorators, per the PEP, is to support applications like embedding type signatures and so forth into the language, attaching them to the relevant objects. A goal could be to support at least partial compilation with (some, and optional) compile-time guarantees about type safety and behavior. To the extent that decorators either (a) are evaluated conditionally based on runtime state, or (b) modify and are in turn modified by runtime state, decorators as mutable environments or with true side effects make a mess of that. Perhaps you don't really want "decorators" for that --- perhaps you want "macros." Subtle but IMHO important distinction. NB, I'm no type Nazi. But I'd really like to see (a) Python implemented in Python, someday --- it would make playing with all these sorts of languages ideas and moving the language along a LOT easier, and (b) Python compiling easily to (and running well on) arbitrary machines or virtual machines. The more runtime / imperative / conditional / side-effecting behavior we allow for decorators, the more difficult both of those goals become. Not trying (heroically or otherwise) to keep language features from being abused as much as simply ask the question "have we REALLY thought through the use cases, the alternatives, and their impact on the future of the language." jb

Jeff Bone <jbone@place.org> writes:
NB, I'm no type Nazi.
NB: I am one. -- Dave Abrahams Boost Consulting http://www.boost-consulting.com

At 04:52 PM 6/25/04 -0400, Brian L. wrote:
This particular issue can be remedied in two ways. First off, a list of decorators with brackets, as Phillip's solution makes possible,
I think David Abrahams should be the one to get the credit: it was his idea to use the tracing machinery, while I just implemented a rough proof-of-concept. I could have implemented it a year ago, had I thought of it before now. So, that makes it *his* solution as far as I'm concerned, and thus you can blame him for any resulting consequences. <wink> (Similarly, the concept of using _getframe() magic and __metaclass__ munging to implement class decorators was originally proposed by Jim Fulton and Steve Alexander; I just developed a robust, co-operative, and general-purpose implementation of the concept for Zope and PyProtocols.)

On Jun 25, 2004, at 3:52 PM, Brian L. wrote:
such a bad way. The brackets actually set off the decorators nicely. Something like:
[decorators] def func(args): pass
+1 IMHO, making decorators functions is a bad idea. Decorators are metadata about the function to which they refer. Using the same syntax for decorators as for functions conceptually weakens this distinction and encourages misuse of decorators, even to the point of potentially encouraging (or implying) side-effects as a result of decoration. IMHO, jb

At 05:53 PM 6/25/04 -0500, Contempt for Meatheads wrote:
IMHO, making decorators functions is a bad idea. Decorators are metadata about the function to which they refer. Using the same syntax for decorators as for functions conceptually weakens this distinction and encourages misuse of decorators, even to the point of potentially encouraging (or implying) side-effects as a result of decoration.
Side-effects are desired by various current users of decorators, so whether this will "encourage" them is moot. Further, those uses often suggest that "metadata" is far too narrow of a word to encompass the desired uses of decorators. Last, keep in mind that there is nothing stopping people today from doing whatever they want with functions *anyway*, in every version of Python I've ever used (back to 1.4): def foo(...): ... do_something_with(foo) So, again, the idea that this somehow becomes a bad thing when it goes at the top of the function definition -- where it should be more visible to the reader that it's taking place -- seems a bit odd to me.

I could also see the function call version: from decorators import decorate ... def foo (bar): decorate(bletch, flappy) ... where decorate is automatically applied to "foo". Bill

At 09:47 PM 6/25/04 -0700, Bill Janssen wrote:
I could also see the function call version:
from decorators import decorate ... def foo (bar): decorate(bletch, flappy) ...
where decorate is automatically applied to "foo".
That won't work, since by the time the function is called it's too late to redefine foo.

Yes, it would have to be a 'compile-time' macro of some sort. Bill
At 09:47 PM 6/25/04 -0700, Bill Janssen wrote:
I could also see the function call version:
from decorators import decorate ... def foo (bar): decorate(bletch, flappy) ...
where decorate is automatically applied to "foo".
That won't work, since by the time the function is called it's too late to redefine foo.

We've now had so many suggestions for decorators that I don't remember whether the following one was suggested already (and shot down, causing the ill-informed maker of the suggestion to go hide under a stone for weeks:-), so here goes: def: objc_signature('i@:if') as methodWithInt_andFloat_(self, intarg, floatarg): return intarg + int(floatarg) The use of "as" here is gratuitous, because everyone seems to use that as the only semi-keyword in existence. -- Jack Jansen, <Jack.Jansen@cwi.nl>, http://www.cwi.nl/~jack If I can't dance I don't want to be part of your revolution -- Emma Goldman

"Fred L. Drake, Jr." <fdrake@acm.org> writes:
On Friday 25 June 2004 04:09 pm, Phillip J. Eby wrote:
* Whether to have a 'decorate' function at all, or whether it's better to just list specialized decorator objects/functions (I generally favor the latter, since 'decorate' is IMO a dead chicken that hasn't appeared in any widely-supported syntax proposals to date).
Ooohh... time for some fun! Some proposals have included "as". Since "as" isn't really a keyword, how about:
from decorate import decorate as as
as(classmethod) def myAlternateConstructor(...): # make something interesting: return 42
Too bad this is ugly for the simple attribute decorator:
as(spam="fidget") def foo(...): send_fred_more_spam_please()
I don't find it so ugly... but then there's always "with" ;-) -- Dave Abrahams Boost Consulting http://www.boost-consulting.com

"Phillip J. Eby" <pje@telecommunity.com> writes:
+classmethod +attrs(spam="fidget") def foo(...): ...
[classmethod(), attrs(spam="fidget")] def foo(...): ...
~classmethod ~attrs(spam="fidget") def foo(...): ...
-classmethod -attrs(spam="fidget") def foo(...): ...
All of these are possible, and each likely to have *some* proponents. Oh, and let's not forget the question of application order of the decorations. :) I personally favor a(b(c(...))) ordering for decorations that are listed before the 'def', even though I favor a(), b(), c() ordering for decorations that come after.
I don't think any of these syntaxes allow classmethod to remain what it is, a "regular" function... not that I consider it a prerequisite or anything. -- Dave Abrahams Boost Consulting http://www.boost-consulting.com

"Phillip J. Eby" <pje@telecommunity.com> writes:
* Which of the numerous possible syntax variations should be used/recommended:
Just a reminder, but I believe that Guido has already pronounced to the effect that there are only 3 contenders left: @staticmethod def foo(a,b,c): pass [staticmethod] def foo(a,b,c): pass and def foo(a,b,c) [staticmethod]: pass Hey - guess what? Writing them out like that, I find that my initial preference for the 3rd one has gone. To be honest, I no longer have a strong opinion between the three. In fact, if anything, I'd say I have a very mild preference for Guido's option (number 2). I dislike the ambiguity problem with it, though. Paul -- "Bother," said the Borg, "We've assimilated Pooh."

At 04:27 PM 6/26/04 +0100, Paul Moore wrote:
"Phillip J. Eby" <pje@telecommunity.com> writes:
* Which of the numerous possible syntax variations should be used/recommended:
Just a reminder, but I believe that Guido has already pronounced to the effect that there are only 3 contenders left:
Those are based on changing the language's actual syntax. The Dave Abrahams solution for decoration allows any currently-legal execution syntax to be used in today's Python versions.
@staticmethod def foo(a,b,c): pass
[staticmethod] def foo(a,b,c): pass
and
def foo(a,b,c) [staticmethod]: pass
Hey - guess what? Writing them out like that, I find that my initial preference for the 3rd one has gone. To be honest, I no longer have a strong opinion between the three. In fact, if anything, I'd say I have a very mild preference for Guido's option (number 2). I dislike the ambiguity problem with it, though.
IMO, the ambiguity is resolved if some kind of visible invocation occurs, e.g.: [declare_staticmethod()] def foo(a,b,c): pass While '[staticmethod]' by itself may appear to be a useless and ignorable statement, adding the () makes it clear that you have to know what the called function *does* before you could consider removing the statement. When you add in the fact that this syntax can be made to work in Python 2.2 and better, not just 2.4, it makes the syntax a big winner in my book. I would like to reverse the application order, though: Guido's patch uses the same order for syntax 2 as for syntax 3, and now that I've played with syntax 2 "for real", I think that right-to-left application should be used for any syntax where the decorators come before the 'def'.

"Phillip J. Eby" <pje@telecommunity.com> writes:
@staticmethod def foo(a,b,c): pass
[staticmethod] def foo(a,b,c): pass
and
def foo(a,b,c) [staticmethod]: pass
Hey - guess what? Writing them out like that, I find that my initial preference for the 3rd one has gone. To be honest, I no longer have a strong opinion between the three. In fact, if anything, I'd say I have a very mild preference for Guido's option (number 2). I dislike the ambiguity problem with it, though.
IMO, the ambiguity is resolved if some kind of visible invocation occurs, e.g.:
[declare_staticmethod()] def foo(a,b,c): pass
While '[staticmethod]' by itself may appear to be a useless and ignorable statement, adding the () makes it clear that you have to know what the called function *does* before you could consider removing the statement.
When you add in the fact that this syntax can be made to work in Python 2.2 and better, not just 2.4, it makes the syntax a big winner in my book.
I would like to reverse the application order, though: Guido's patch uses the same order for syntax 2 as for syntax 3, and now that I've played with syntax 2 "for real", I think that right-to-left application should be used for any syntax where the decorators come before the 'def'.
Here's another syntactic idea that could be made to work without core language changes: [as.staticmethod] def foo(a,b,c): pass I think that one's pretty sweet. It can be extended any number of ways: [as.staticmethod, as.classmethod, ...] def foo(a,b,c): pass [as.staticmethod|classmethod| ...] def foo(a,b,c): pass -- Dave Abrahams Boost Consulting http://www.boost-consulting.com

On Sat, 2004-06-26 at 11:27, Paul Moore wrote:
Just a reminder, but I believe that Guido has already pronounced to the effect that there are only 3 contenders left:
I've said before that I'm past caring which syntax is chosen. I could live with any of these three. I just want the feature in 2.4 and I trust Guido to Make The Right Decision. -Barry

Barry Warsaw <barry@python.org> writes:
On Sat, 2004-06-26 at 11:27, Paul Moore wrote:
Just a reminder, but I believe that Guido has already pronounced to the effect that there are only 3 contenders left:
I've said before that I'm past caring which syntax is chosen. I could live with any of these three. I just want the feature in 2.4 and I trust Guido to Make The Right Decision.
A-fucking-men. Cheers, mwh (who apologies for the crudity, but would also like an apology from all the people who bugged him about this topic at EuroPython (apart from Guido :-)) -- Just point your web browser at http://www.python.org/search/ and look for "program", "doesn't", "work", or "my". Whenever you find someone else whose program didn't work, don't do what they did. Repeat as needed. -- Tim Peters, on python-help, 16 Jun 1998

"Phillip J. Eby" <pje@telecommunity.com> writes:
At 01:26 PM 6/24/04 -0400, David Abrahams wrote:
Anthony Baxter <anthony@interlink.com.au> writes:
So, let the floodgates open. Remember, we _can_ change this any time up until 2.4b1, if there's a decision that the chosen form sucks. :-)
Here's thinking in a different direction altogether:
No special syntax
Instead, expose enough functionality in standard library functions that an appropriately-written pure-python "decorate" function can do it.
decorate(staticmethod, my_decorator) def f(x): whatever()
Since function definitions are executable statements, it should in principle be possible to arrange something that allows one to hook the execution of that statement. Perhaps it's already doable with the debugger hook?
Hmmm. You probably *could* create such a function in CPython, maybe even as far back as 2.1 (since all it needs is sys._getframe and the tracing hook), but it wouldn't be portable to Jython.
And, boy, would it be a sick and twisted piece of code.
What you wrote doesn't look all *that* awful.
You'd need to do something like keep a copy of the locals and on each "new line" event from the trace hook, you'd need to both call the old trace hook (so that debugging could still take place) and check to see if the locals had a new function object. As soon as the function object appeared, you could do your dirty work (after first restoring the *old* value bound to that name, so the decorators would have access to the previous binding of the name).
I'm perfectly happy for this decorate function to work on the very next name binding; I don't think it's reasonable to expect to be able to do anything else between it and the function definition.
About the only bit you couldn't do in pure Python would be decorating a function defined in a nested scope, because "fast locals" aren't accessible from pure Python.
You're beyond my expertise here.
But for module-level functions and methods in classes, this might actually work. Heck, I'm rather tempted to try to write it and see if it does. At worst, it might be a fun way to learn the ins and outs of sys.settrace().
<snip>
Anyway, it works with CPython 2.2. Caveat: do *not* use listcomps or any other assignment statements between your 'decorate()' invocation and the function definition, or the decorators will be applied to the bound-to variable!
Personally, I think the 'decorate()' function is probably actually not that useful, compared to making specialized decoration functions like, say:
classmethod_follows() def foo(klass, x, y, z): # ...
Ick!
Only with better names. :) Also, using this approach means you can do something like:
[classmethod_follows()]
Ick in brackets ;-> ! -- Dave Abrahams Boost Consulting http://www.boost-consulting.com

At 08:26 AM 6/25/04 -0400, David Abrahams wrote:
"Phillip J. Eby" <pje@telecommunity.com> writes:
You'd need to do something like keep a copy of the locals and on each "new line" event from the trace hook, you'd need to both call the old trace hook (so that debugging could still take place) and check to see if the locals had a new function object. As soon as the function object appeared, you could do your dirty work (after first restoring the *old* value bound to that name, so the decorators would have access to the previous binding of the name).
I'm perfectly happy for this decorate function to work on the very next name binding; I don't think it's reasonable to expect to be able to do anything else between it and the function definition.
Keep in mind that this means code like this: decorate(foo) decorate(bar(baz=[x*2 for x in y])) def spam(): pass Will likely fail horribly, applying the 'foo' decorator to the first element of 'y' before trying to multiply by 2, or worse, possibly applying them to the "anonymous" list object that's created to hold the results of the list comprehension. Or, it might sometimes *seem* to work due to hashing order and the vagaries of when 'line' events fire, and then fail in a new release of Python. This is why I consider it somewhat of a wart in the current implementation, as I note below...
Anyway, it works with CPython 2.2. Caveat: do *not* use listcomps or any other assignment statements between your 'decorate()' invocation and the function definition, or the decorators will be applied to the bound-to variable!
Personally, I think the 'decorate()' function is probably actually not that useful, compared to making specialized decoration functions like, say:
classmethod_follows() def foo(klass, x, y, z): # ...
Ick!
Is it the name you dislike? Or the idea of simply invoking decorating functions directly?
Only with better names. :) Also, using this approach means you can do something like:
[classmethod_follows()]
Ick in brackets ;-> !

"Phillip J. Eby" <pje@telecommunity.com> writes:
At 08:26 AM 6/25/04 -0400, David Abrahams wrote:
"Phillip J. Eby" <pje@telecommunity.com> writes:
You'd need to do something like keep a copy of the locals and on each "new line" event from the trace hook, you'd need to both call the old trace hook (so that debugging could still take place) and check to see if the locals had a new function object. As soon as the function object appeared, you could do your dirty work (after first restoring the *old* value bound to that name, so the decorators would have access to the previous binding of the name).
I'm perfectly happy for this decorate function to work on the very next name binding; I don't think it's reasonable to expect to be able to do anything else between it and the function definition.
Keep in mind that this means code like this:
decorate(foo) decorate(bar(baz=[x*2 for x in y])) def spam(): pass
Will likely fail horribly, applying the 'foo' decorator to the first element of 'y' before trying to multiply by 2, or worse, possibly
"doctor, it hurts when I do this..."
applying them to the "anonymous" list object that's created to hold the results of the list comprehension. Or, it might sometimes *seem* to work due to hashing order and the vagaries of when 'line' events fire, and then fail in a new release of Python. This is why I consider it somewhat of a wart in the current implementation, as I note below...
That's why decorate should take a variable number of arguments: decorate(foo, bar(baz=[x*2 for x in y])) def spam(): pass
Anyway, it works with CPython 2.2. Caveat: do *not* use listcomps or any other assignment statements between your 'decorate()' invocation and the function definition, or the decorators will be applied to the bound-to variable!
Personally, I think the 'decorate()' function is probably actually not that useful, compared to making specialized decoration functions like, say:
classmethod_follows() def foo(klass, x, y, z): # ...
Ick!
Is it the name you dislike? Or the idea of simply invoking decorating functions directly?
What I want is a recognizable syntax that indicates decoration. If it's likely to be any old function name, decorators are much less likely to jump out at you. In fact, that's why I really like operator syntaxes like: with[foo, bar(baz=[x*2 for x in y])] def spam(): pass or something. -- Dave Abrahams Boost Consulting http://www.boost-consulting.com

Anthony Baxter <anthony@interlink.com.au>:
So, who's feeling adventurous? I'm convinced that this should go into 2.4 if possible
I'd hate for this to be rushed and end up freezing something into the language that was later regretted. My feeling is that nobody's had enough time to absorb the latest swerve that the discussion has taken. We seemed to be converging on a solution everyone would be reasonably happy with, and then Guido suddenly introduced something completely off-the-wall. I think a pause for thought is needed. Greg Ewing, Computer Science Dept, +--------------------------------------+ University of Canterbury, | A citizen of NewZealandCorp, a | Christchurch, New Zealand | wholly-owned subsidiary of USA Inc. | greg@cosc.canterbury.ac.nz +--------------------------------------+

Anthony Baxter wrote:
So here's the state of play with decorators and 2.4.
[snip]
Channelling Guido, via his EP keynote (http://www.python.org/doc/essays/ppt/euro2004/euro2004.ppt) I'm assuming that "Java-style" is something like:
@staticmethod def blah(args): body
@funcattrs(vegetable="blah", author="GvR") def blah2(args): body
I'd like to cast a -1 vote in the form of a small plea. It turns out that IPython (http://ipython.scipy.org) uses @ as a special character to identify 'magic' commands which control the shell itself. If this became valid syntax, IPython would require not only internal rewrites but also a fair amount of users would have to change their working habits. I'm fully aware that the core python development won't be held back by third-party tools which use non-standard syntax. But at least I'd like to cast a voice, in case the argument is in the end narrowly decided between this and other alternatives. IPython has become a fairly popular tool, judging by a quick google search, and the fact that it's distributed in SUSE 9.1, as well as being available for Debian, OSX (via Fink), Gentoo and other smaller Linux distributions. There are also several projects (mainly in scientific computing) which use it as their interactive environments (I designed it deliberately trying to make this kind of use very easy). IPython is also available for Windows, and using Gary Bishop's readline library it provides full ANSI coloring and readline editing support. With current (and coming) CVS, it will grow an extension (pysh) for use as a system-shell with python syntax, a long-requested feature from many users. I completely understand that I can't pressure any of this decision, since I chose an unused but by no means 'reserved' character. But at least I'd like to let the core team know about this particular side-effect, which will impact a fair amount of users out there. Regards to all, Fernando.
participants (22)
-
Aahz
-
Anthony Baxter
-
Armin Rigo
-
Barry Warsaw
-
Bill Janssen
-
Bob Ippolito
-
Brian L.
-
Contempt for Meatheads
-
David Abrahams
-
David Eppstein
-
Fernando Perez
-
Fred L. Drake, Jr.
-
Greg Ewing
-
Guido van Rossum
-
Jack Jansen
-
Jeff Bone
-
Jeff Bone
-
Mark Russell
-
Michael Hudson
-
Paul Moore
-
Paul Prescod
-
Phillip J. Eby