RE: [Python-Dev] Re: decorators and 2.4
I'm really curious what these uses might be. Folks keep saying this kind of thing, but when you dig in IMHO most of these actually seem to be design-time things, statements *about* the intent or use of the function (relative to the language runtime itself, e.g. classmethod and so forth, or relative to a framework, such as registering a function, or relative to some tool, e.g. for extended documentation purposes or type-checking, etc.) rather than e.g. runtime modifications to its behavior.
Well, perhaps you and I mean slightly different things when we talk
about "declarative" functionality. Here are some uses, you let me
know whether they are "declarative" or not:
1. Registering with a framework:
[FooFramework]
def some_func():
body()
FooFramework() "registers" the function with a framework by
simply adding it to a list of known functions. This allows
the GUI interface for FooFramework to display the function.
2. Take actions before or after a call:
[traceable]
def some_func():
body()
traceable() replaces some_func() with a function which
may write a log message before and after actually
invoking the original some_func(). Whether the log
message is actually written is controlled by a list of
function names (compared to some_func.__name__) and
that list may change dynamically while the program is
executing.
3. Enforce pre- and post- conditions:
[precondition(lambda x,y: x (1) Does a function have access to its own decorators? I wouldn't think so. They are NOT "metadata", that's just
one possible use. The function no more has access to its
decorators than it has access to its source code! If we
just needed a place to stick metadata we'd use the
function's __dict__. (2) Do decorators define literal constant values at
design-time or do
they define a scope of mutable bindings? The decorators are simply functions that get executed. x = "Guido van Rossum"
def mymethod(f) [attrs(versionadded="2.2", author=x)]: That's fine. author is set to Guido. x = "Guido van Rossum"
def mymethod(f) [attrs(versionadded="2.2", author=x)]:
oldAuthor = someHandleToMyDecorators.atts.author
someHandleToMyDecorators.atts.author = "Eric Idle" That's meaningless unless I know what "someHandleToMyDecorators"
means. IMHO, the RHS of an "assignment" in a decorator should only be some
value that can be known at "compile" time I disagree. I think you and I just have different ideas about
what we'd use decorators for. (Actually, I think that perhaps
your set of intended uses is a subset of mine.) (3) Should it be possible to conditionally evaluate decorators based
on run-time state? The following, IMHO, is truly scary and inhibits many of the intended
uses of decorators. This shouldn't be legal, ever: if foo = 3:
[someDecoratorThatModifiesBarsRuntimeBehavior]
else:
[someOtherDecorator]
def bar(...):
... Hmm... I hadn't considered that one. I'll reserve judgment.
I wouldn't mind if it worked, but I'd certainly try never to
use it. I *WOULD* however make use of things like this:
if foo = 3:
standard_decoration = someDecoratorWithALongName
else:
standard_decoration = someOtherDecorator
# ...
[standard_decoration]
def bar(...):
...
You write "The following, IMHO, is truly scary and inhibits many
of the intended uses of decorators". I'm not sure that intended
uses it is inhibiting. The only one I can imagine is using
decorators to create a type-aware compiler that generates type-
specific optimized code. I don't think this IS a possible use
for decorators. So... what use *would* be inhibited if
decorators were used in this manner?
-- Michael Chermside
This email may contain confidential or privileged information. If you believe you have received the message in error, please notify the sender and delete the message without copying or disclosing it.
On Jun 28, 2004, at 1:16 PM, Chermside, Michael wrote:
1. Registering with a framework: [FooFramework] def some_func(): body()
2. Take actions before or after a call: [traceable] def some_func(): body()
3. Enforce pre- and post- conditions: [precondition(lambda x,y: x
4. Storing Private Data: class Some_Class: [private] def some_method(self, private, x): body(self, private, x)
All of these have the effect of either "wrapping" the function (i.e., can be thought of as higher-order functions that take functions and return functions with the same type as the input function) or "bracketing" the function call (in the case of your pre- and post. Subtly different from the former.) The former's trivial to deal with, semantically; the latter slightly less so but tractable. I would say that all such uses --- when the "outer" function doesn't impact state or type of the wrapped function, except perhaps to extend it (w/, e.g., exceptions, such as potentially in pre- and post-) --- can more of less be thought of as "declarative." (Side note: were Python to be extended with true anonymous functions, this would all be rendered moot; the result would IMHO be both syntactically and semantically clearer.)
You follow with a few questions:
(1) Does a function have access to its own decorators?
I wouldn't think so. They are NOT "metadata", that's just one possible use. The function no more has access to its decorators than it has access to its source code! If we just needed a place to stick metadata we'd use the function's __dict__.
+1. I waffle on this. In general I'd feel better if functions etc. don't have access to their own decorators at runtime, though you can make the general argument that introspection is a good thing.
(2) Do decorators define literal constant values at design-time or do they define a scope of mutable bindings?
The decorators are simply functions that get executed.
IMHO, that's one way to think about it. From what I can tell from the discussion, the PEP, and reading between the lines, there are three legit uses and a few wacky ones. The legit and common / consensus ones seem to be: (1) Wrap a function w/o impacting its internal runtime scope (2) Provide pre- and post- about a function (3) Provide metadata (despite the claim above, the below is IMHO a metadata application.)
x = "Guido van Rossum" def mymethod(f) [attrs(versionadded="2.2", author=x)]:
That's fine. author is set to Guido.
This is bad because it might inhibit typing applications. Consider this from the PEP: def func(arg1, arg2) [accepts(int, (int,float)), returns((int,float))]: return arg1 * arg2 Should I be able to DYNAMICALLY define the type signature of the function at runtime? Consider, say: if todayIsThursday(): mysig = (int, (int, float)) else: mysig = (str, (int, float)) [accepts(sig)] def func(arg1, arg2): if todayIsThursday(): return foo() # foo is (int, (int, float)) else: return bar() # bar is (str, (int, float)) That's worse than useless. :-/
x = "Guido van Rossum" def mymethod(f) [attrs(versionadded="2.2", author=x)]: oldAuthor = someHandleToMyDecorators.atts.author someHandleToMyDecorators.atts.author = "Eric Idle"
That's meaningless unless I know what "someHandleToMyDecorators" means.
Right, but that's my point. Dangerous to give the handle in the first place, even more so if you can set things up dynamically based on runtime state, much more so if you can mutate those bindings once they're set, *particularly* if you can do some from within the scope of the thing being decorated. BTW, call "someHandleToMyDecorators" "self" or "__dict__" and think about it...
if foo = 3: [someDecoratorThatModifiesBarsRuntimeBehavior] else: [someOtherDecorator] def bar(...): ...
Hmm... I hadn't considered that one. I'll reserve judgment. I wouldn't mind if it worked, but I'd certainly try never to use it. I *WOULD* however make use of things like this:
if foo = 3: standard_decoration = someDecoratorWithALongName else: standard_decoration = someOtherDecorator # ... [standard_decoration] def bar(...): ...
Ugh. Slippery slope to #ifdef / macro hell ala Pike et. al. But even worse: at least #ifdef can be evaluated statically! :-/
You write "The following, IMHO, is truly scary and inhibits many of the intended uses of decorators". I'm not sure that intended uses it is inhibiting. The only one I can imagine is using decorators to create a type-aware compiler that generates type- specific optimized code. I don't think this IS a possible use for decorators. So... what use *would* be inhibited if decorators were used in this manner?
Exactly that. As for that being an anticipated use, it's at least implied in the PEP (see above example from the PEP) and indeed something I've been fiddling with a bit. Point in all of this being: the semantics of decorators are largely not defined at this point; any such description appears to be an anecdotal side-effect of the implementations people are playing with --- there's certainly no normative description of their intended semantics, either in the PEP of elsewhere at this point to my knowledge. IMHO, decorators are a GOOD idea; but the syntax of decorators is in my opinion FAR less of a concern than should be their semantics. jb
participants (2)
-
Chermside, Michael
-
Jeff Bone