Jacob Holm wrote: > Pascal Chambon wrote: >> One last idea I might have : what about something like
>> * def myfunc(a, b, c = yield []): >> pass*
>> [...], but there is no interpretation conflict for the parser, and we >> might quickly get used to it
> I am surprised that there is no conflict, but it looks like you are > technically right. The parentheses around the yield expression are > required in the following (valid) code:
> I would hate to see the meaning of the above change depending on whether > the parentheses around the yield expression were there or not, so -1 on > using "yield" for this.
> I'm +0 on the general idea of adding a keyword for delayed evaluation of > default argument expressions.
To someone who's a novice to this, could someone explain to me why it has to be an existing keyword at all? Since not identifiers are valid in that context anyway, why couldn't it be a new keyword that can still be used as an identifier in valid contexts? For example (not that I advocate this choice of keyword at all):
def foo(bar reinitialize_default []): # <-- it's a keyword here reinitialize_default = "It's an identifier here!"
That would be a syntax error now and if it were defined as a keyword only in that context it wouldn't introduce backwards compatibility problems and wouldn't force us to reuse an existing keyword in a context that may be a bit of a stretch.
Is there a reason that this wouldn't be a viable approach?
On 2009-05-13, MRAB <goo...@mrabarnett.plus.com> wrote:
> Jacob Holm wrote: >> Pascal Chambon wrote: >>> One last idea I might have : what about something like
>>> * def myfunc(a, b, c = yield []): >>> pass*
>>> [...], but there is no interpretation conflict for the parser, and we >>> might quickly get used to it
>> I am surprised that there is no conflict, but it looks like you are >> technically right. The parentheses around the yield expression are >> required in the following (valid) code:
>> I would hate to see the meaning of the above change depending on whether >> the parentheses around the yield expression were there or not, so -1 on >> using "yield" for this.
>> I'm +0 on the general idea of adding a keyword for delayed evaluation of >> default argument expressions.
On Wed, May 13, 2009 at 10:52 AM, Jeremy Banks <jer...@jeremybanks.ca> wrote: > To someone who's a novice to this, could someone explain to me why it > has to be an existing keyword at all? Since not identifiers are valid > in that context anyway, why couldn't it be a new keyword that can > still be used as an identifier in valid contexts? For example (not > that I advocate this choice of keyword at all):
> def foo(bar reinitialize_default []): # <-- it's a keyword here > reinitialize_default = "It's an identifier here!"
> That would be a syntax error now and if it were defined as a keyword > only in that context it wouldn't introduce backwards compatibility > problems and wouldn't force us to reuse an existing keyword in a > context that may be a bit of a stretch.
> Is there a reason that this wouldn't be a viable approach?
Traditionally, keywords are recognized at the lexer level, which then passes tokens to the parser. Lexers are pretty simple (typically constants and regular expressions) and don't take the context into account. In principle what you're saying could work, but given the significant reworking of the lexer/parser it would require, it's quite unlikely to happen, for better or for worse.
Le Wed, 13 May 2009 11:52:57 -0300, Jeremy Banks <jer...@jeremybanks.ca> s'exprima ainsi:
> To someone who's a novice to this, could someone explain to me why it > has to be an existing keyword at all? Since not identifiers are valid > in that context anyway, why couldn't it be a new keyword that can > still be used as an identifier in valid contexts? For example (not > that I advocate this choice of keyword at all):
> def foo(bar reinitialize_default []): # <-- it's a keyword here > reinitialize_default = "It's an identifier here!"
> That would be a syntax error now and if it were defined as a keyword > only in that context it wouldn't introduce backwards compatibility > problems and wouldn't force us to reuse an existing keyword in a > context that may be a bit of a stretch.
> Is there a reason that this wouldn't be a viable approach?
My opinion on this is you're basically right. Even 'print' (for py<3.0) could be an identifier you could use in an assignment (or in any value expression), I guess, for parse patterns are different: print_statement : "print" expression assignment : name '=' expression So you can safely have "print" as name, or inside an expression. Even "print print" should work !
But traditionnally grammars are not built as a single & total definition of the whole language (like is often done using e.g. PEG, see http://en.wikipedia.org/wiki/Parsing_Expression_Grammar) but as a 2-layer definition: one for tokens (lexicon & morphology) and one for higher-level patterns (syntax & structure). The token layer is performed by a lexer that will not take the context into account to recognize tokens, so that it could not distinguish several, syntactically & semantically different, occurrences of "print" like above. As a consequence, in most languages, key word = reserved word
spir wrote: > My opinion on this is you're basically right. Even 'print' (for py<3.0) could be an identifier you could use in an assignment (or in any value expression), I guess, for parse patterns are different: > print_statement : "print" expression > assignment : name '=' expression > So you can safely have "print" as name, or inside an expression. Even "print print" should work !
But you would not want print print and print(print) to have two different meanings. In Python, extra parens are fair around expressions, and print(print) is clearly a function call.
> Well, since adding new keywords or operators is very sensitive, and the > existing ones are rather exhausted, it won't be handy to propose a new > syntax...
> One last idea I might have : what about something like
> * def myfunc(a, b, c = yield []): > pass*
> I'm not expert in english, but I'd say the following "equivalents" of > yield (dixit WordWeb) are in a rather good semantic area : > *Be the cause or source of > *Give or supply > *Cause to happen or be responsible for > *Bring in
> Of course the behaviour of this yield is not so close from the one we > know, but there is no interpretation conflict for the parser, and we > might quickly get used to it : > * yield in default argument => reevaluate the expression each time > * yield in function body => return value and prepare to receive one
> How do you people feel about this ? > Regards, > Pascal
I'm not a fan. If you thought not reevaluating function expressions was confusing for newbies, wait until you see what making up a new kind of yield will do for them.
Why not just push for some decorators that do this to be included in stdlib? I see the utility, but not the point of adding extra syntax.
>>> @Runtime
... def f(x=a**2+2b+c): ... return x ...
>>> a = 1 >>> b = 2 >>> c = 3 >>> f()
8
This seems much more intuitive and useful to me than adding new meanings to yield.
> Why not just push for some decorators that do this to be included in > stdlib? I see the utility, but not the point of adding extra syntax.
>>>> @Runtime > ... def f(x=a**2+2b+c): > ... return x > ... >>>> a = 1 >>>> b = 2 >>>> c = 3 >>>> f() > 8
> This seems much more intuitive and useful to me than adding new > meanings to yield.
This is not possible.
def f(x=a**2+2*b+c): return x
is compiled to something very much like:
_tmp = x**2+2*b+c def f(x=_tmp): return x
So it is impossible to find out what expression yields the default value of x by just looking at f. You have to use lambda or use George Sakkis' idea of using strings for defaults and evaluating them at call- time (but I'm not sure this will work reliably with nested functions).
> Anyhow, since you've decided you want add a new kind of default > argument spelled differently than the standard one, I have to ask what > this buys you beyond
> def foo(): > def myfunc(a, b, c = None): > if c is not None: # or a setinel > c = []
> Sure, it takes two more lines of code to write this (three if you want > to use an object() instance as your sentinel to allow None being > passed in), but does not require a new bit of language syntax for > every Python programmer to learn...
> Greg F
Well, not only do we have to write more code, but we lose in self-documentation I guess (I'd rather have the default appearing in the signature than in the function code - pydoc and stuffs won't notice it), and I find slightly disappointing the principle of a "sentinel", i.e "I would have wanted to do something there but I can't so I'll do it farther, take that in the meantime".
> There's the suggestion that Carl Johnson gave:
> def myfunc(a, b, c else []): > pass
> or there's:
> def myfunc(a, b, c def []): > pass
> where 'def' stands for 'default' (or "defaults to").
Those phrases could do it, I'm just worryied about the fact that semantically, they make (to me) no difference with "c = []". None of those ways looks more "dynamic" than teh other, so it might be hard to explain why "=" means compiel time, and "def" means runtime.
Jeremy Banks wrote: > To someone who's a novice to this, could someone explain to me why it > has to be an existing keyword at all? Since not identifiers are valid > in that context anyway, why couldn't it be a new keyword that can > still be used as an identifier in valid contexts? For example (not > that I advocate this choice of keyword at all):
> def foo(bar reinitialize_default []): # <-- it's a keyword here > reinitialize_default = "It's an identifier here!"
> That would be a syntax error now and if it were defined as a keyword > only in that context it wouldn't introduce backwards compatibility > problems and wouldn't force us to reuse an existing keyword in a > context that may be a bit of a stretch.
> Is there a reason that this wouldn't be a viable approach?
At one time, 'as' was only a keyword in the context of import. So it is 'viable'. But it was a bit confusing for programmers and messy implementation-wise and I think the developers were glad to promote 'as' to a full keyword and would be reluctant to go down that road again.
MRAB wrote: > There's the suggestion that Carl Johnson gave:
> def myfunc(a, b, c else []): > pass
> or there's:
> def myfunc(a, b, c def []): > pass
> where 'def' stands for 'default' (or "defaults to").
I had the idea of def f(c=:[]): where ':' is intended to invoke the idea of lambda, since the purpose is to turn the expression into a function that is automatically called (which is why lambda alone is not enough). So I would prefer c = def [] where def reads 'auto function defined by...'.
or c = lambda::[] where the extra ':' indicates that that the function is auto-called
or c = lambda():[], (now illegal), where () is intended to show that the default arg is the result of calling the function defined by the expression. lambda:[]() (now legal) would mean to (uselessly) call the function immediately.
Thinking about it, I think those who want a syntax to indicate that the expression should be compiled into a function and called at runtime should build on the existing syntax (lambda...) for indicating that an expression should be compiled into a function, rather than inventing a replacement for that.
The use of the lambda keyword here makes the scope of any variables in the expression clear. The use of the prefix * makes the syntax invalid today, suggests dereferencing and doesn't hide the overhead. This is equivalent to:
__unset = object() __default = lambda: expression def mfunc(a, b, c = __unset): if c == __unset: c = __default() stuff
> If you thought not reevaluating function expressions > was confusing for newbies, wait until you see what making up a new > kind of yield will do for them.
> Why not just push for some decorators that do this to be included in > stdlib? I see the utility, but not the point of adding extra syntax.
Even if a decorator solution can be made to work, it seems to me that the difficulty with a decorator solution is that it is all-or-nothing -- you can decorate the entire parameter list, or none of the parameters, but not some of the parameters. You can bet that people will say they want delayed evaluation of some default arguments and compile-time evaluation of others, in the same function definition.
There are work-arounds, of course, but there are perfectly adequate work-arounds for the lack of delayed evaluation defaults now, and it hasn't stopped the complaints.
I'm going to suggest that any syntax should be applied to the formal parameter name, not the default value. This feels right to me -- we're saying that it's the formal parameter that is "special" for using delayed semantics, not that the default object assigned to it is special. Hence it should be the formal parameter that is tagged, not the default value.
By analogy with the use of the unary-* operator, I suggest we use a new unary-operator to indicate the new semantics. Inside the parameter list, &x means to delay evaluation of the default argument to x to runtime:
def parrot(a, b, x=[], &y=[], *args, **kwargs):
As a bonus, this will allow for a whole new series of bike-shedding arguments about which specific operator should be used. *grin*
Tagging a parameter with unary-& but failing to specify a default value should be a syntax error:
Steven D'Aprano wrote: > On Thu, 14 May 2009 05:18:37 am CTO wrote:
>> If you thought not reevaluating function expressions >> was confusing for newbies, wait until you see what making up a new >> kind of yield will do for them.
>> Why not just push for some decorators that do this to be included in >> stdlib? I see the utility, but not the point of adding extra syntax.
> Even if a decorator solution can be made to work, it seems to me that > the difficulty with a decorator solution is that it is > all-or-nothing -- you can decorate the entire parameter list, or none > of the parameters, but not some of the parameters. You can bet that > people will say they want delayed evaluation of some default arguments > and compile-time evaluation of others, in the same function definition.
> There are work-arounds, of course, but there are perfectly adequate > work-arounds for the lack of delayed evaluation defaults now, and it > hasn't stopped the complaints.
> I'm going to suggest that any syntax should be applied to the formal > parameter name, not the default value. This feels right to me -- we're > saying that it's the formal parameter that is "special" for using > delayed semantics, not that the default object assigned to it is > special. Hence it should be the formal parameter that is tagged, not > the default value.
> By analogy with the use of the unary-* operator, I suggest we use a new > unary-operator to indicate the new semantics. Inside the parameter > list, &x means to delay evaluation of the default argument to x to > runtime:
> def parrot(a, b, x=[], &y=[], *args, **kwargs):
> As a bonus, this will allow for a whole new series of bike-shedding > arguments about which specific operator should be used. *grin*
> Tagging a parameter with unary-& but failing to specify a default value > should be a syntax error:
> def parrot(&x, &y=[]):
> Likewise for unary-& outside of a parameter list.
> Bike-shedding away... *wink*
Well, going back to 'def', it could mean 'deferred until call-time':
> def myfunc(a, b, c = *lambda: expression): > stuff
> The use of the lambda keyword here makes the scope of any variables in > the expression clear. The use of the prefix * makes the syntax invalid > today, suggests dereferencing and doesn't hide the overhead.
Why not
@calldefaults def myfunc(a, b, c = lambda: expression): pass
which should be possible without introducing new syntax.
Sure you can do it with a decorator. Although you might want one default to have this behavior and one not to. And putting the * right next to the lambda makes this a bit more explicit to my eye.
Georg Brandl <g.bra...@gmx.net> wrote: >Bruce Leban schrieb:
>> Here's what I'd like:
>> def myfunc(a, b, c = *lambda: expression):
>> stuff
>> The use of the lambda keyword here makes the scope of any variables in
>> the expression clear. The use of the prefix * makes the syntax invalid
>> today, suggests dereferencing and doesn't hide the overhead.
>Why not
>@calldefaults
>def myfunc(a, b, c = lambda: expression):
> pass
>which should be possible without introducing new syntax.
Steven D'Aprano wrote: > On Thu, 14 May 2009 05:18:37 am CTO wrote:
>> If you thought not reevaluating function expressions >> was confusing for newbies, wait until you see what making up a new >> kind of yield will do for them.
>> Why not just push for some decorators that do this to be included in >> stdlib? I see the utility, but not the point of adding extra syntax.
> Even if a decorator solution can be made to work, it seems to me that > the difficulty with a decorator solution is that it is > all-or-nothing -- you can decorate the entire parameter list, or none > of the parameters, but not some of the parameters. You can bet that > people will say they want delayed evaluation of some default arguments > and compile-time evaluation of others, in the same function definition.
Not all or nothing, and selection is easy. A decorator could only call callable objects, and could/should be limited to calling function objects or even function objects named '<lambda>'. And if one wanted the resulting value to such a function, escape the default lambda expression with lambda.
x=[1,2] @call_lambdas def f(a=len(x), lst = lambda:[], func = lambda: lambda x: 2*x): # a is int 2, lst is a fresh list, func is a one-parameter function
> > Why not just push for some decorators that do this to be included in > > stdlib? I see the utility, but not the point of adding extra syntax.
> >>>> @Runtime > > ... def f(x=a**2+2b+c): > > ... return x > > ... > >>>> a = 1 > >>>> b = 2 > >>>> c = 3 > >>>> f() > > 8
> > This seems much more intuitive and useful to me than adding new > > meanings to yield.
> This is not possible.
> def f(x=a**2+2*b+c): > return x
> is compiled to something very much like:
> _tmp = x**2+2*b+c > def f(x=_tmp): > return x
> So it is impossible to find out what expression yields the default > value of x by just looking at f. You have to use lambda or use George > Sakkis' idea of using strings for defaults and evaluating them at call- > time (but I'm not sure this will work reliably with nested functions).
> -- > Arnaud
Thanks for the input, but I've already written the code to do this. It is available at <URL:http://code.activestate.com/recipes/576751/>. For those with hyperlink allergies, the snippet posted above reevaluates the function whenever it is called, and can be used like so:
>>> from runtime import runtime >>> @runtime
... def example1(x, y=[]): ... y.append(x) ... return y ...
>>> example1(1) [1] >>> example1(2)
[2]
or, as posted above,
>>> a, b, c = 0, 1, 2 >>> @runtime
... def example2(x=a**2+2*b+c): ... return x ...
>>> example2() 4 >>> a = 5 >>> example2()
29
The gode given is slow and ugly, but it does appear- at least to me- to do what is being asked here.
> def myfunc(a, b, c = *lambda: expression): > stuff
> The use of the lambda keyword here makes the scope of any variables in > the expression clear. The use of the prefix * makes the syntax invalid > today, suggests dereferencing and doesn't hide the overhead. This is > equivalent to:
There is a proposal, which I thought was accepted in principle, to make '* seq' valid generally, not just in arg-lists. to mean 'unpack the sequence'. * (lambda:1,2)() would then be valid, and without the call, would be a runtime, not syntax error.
Other than that ;0(, it would be an interesting idea.
> __unset = object() > __default = lambda: expression > def mfunc(a, b, c = __unset): > if c == __unset: > c = __default() > stuff
On Wed, May 13, 2009 at 7:08 PM, Terry Reedy <tjre...@udel.edu> wrote: > Bruce Leban wrote:
>> Here's what I'd like:
>> def myfunc(a, b, c = *lambda: expression): >> stuff
>> The use of the lambda keyword here makes the scope of any variables in the >> expression clear. The use of the prefix * makes the syntax invalid today, >> suggests dereferencing and doesn't hide the overhead. This is equivalent to:
> There is a proposal, which I thought was accepted in principle, to make '* > seq' valid generally, not just in arg-lists. to mean 'unpack the sequence'. > * (lambda:1,2)() would then be valid, and without the call, would be a > runtime, not syntax error.
> Other than that ;0(, it would be an interesting idea.
Then how about putting the * before the parameter ?
def myfunc(a, b, *c = lambda: expression):
It's currently a syntax error, although the fact that "*arg" and "*arg=default" would mean something completely different is problematic. Still the same idea can be applied for some other operator (either valid already or not).
Regardless of the actual operator, I came up with the following additional subproposals.
Subproposal (1): Get rid of the explicit lambda for dynamic arguments. That is,
def myfunc(a, b, *x=[]):
would be equivalent to what previous proposals would write as
def myfunc(a, b, *x=lambda: []):
Subproposal (2): If subproposal (1) is accepted, we could get for free (in terms of syntax at least) dynamic args depending on previous ones. That is,
def myfunc(a, b, *m=(a+b)/2):
would mean
def myfunc(a, b, *m = lambda a,b: (a+b)/2):
with the lambda being passed the values of a and b at runtime.
> On Wed, May 13, 2009 at 7:08 PM, Terry Reedy <tjre...@udel.edu> wrote:
> > Bruce Leban wrote: > >> def myfunc(a, b, c = *lambda: expression): > >> stuff
> > There is a proposal, which I thought was accepted in principle, to make > '* > > seq' valid generally, not just in arg-lists. to mean 'unpack the > sequence'. > > * (lambda:1,2)() would then be valid, and without the call, would be a > > runtime, not syntax error.
> Then how about putting the * before the parameter ?
> def myfunc(a, b, *c = lambda: expression):
> It's currently a syntax error, although the fact that "*arg" and > "*arg=default" would mean something completely different is > problematic. Still the same idea can be applied for some other > operator (either valid already or not).
I think similar syntax should do similar things. If *arg means one thing and &arg means something else, that's confusing. There are lots of non confusing alternatives:
def foo(a, b := lambda: bar): def foo(a, b = & lambda: bar): def foo(a, @dynamic b = lambda: bar): # adding decorators on parameters
and more
Subproposal (1): Get rid of the explicit lambda for dynamic arguments. That
> is,
> def myfunc(a, b, *x=[]):
> would be equivalent to what previous proposals would write as
> def myfunc(a, b, *x=lambda: []):
Explicit is better than implicit. There's a thunk getting created here, right? Don't you want that to be obvious? I do.
Subproposal (2): If subproposal (1) is accepted, we could get for free
> (in terms of syntax at least) dynamic args depending on previous ones. > That is,
> def myfunc(a, b, *m=(a+b)/2):
> would mean
> def myfunc(a, b, *m = lambda a,b: (a+b)/2):
> with the lambda being passed the values of a and b at runtime.
It's not free and that adds quite a bit of complexity. Note that default parameters are evaluated in the context of where the function is defined, NOT in the middle of setting the other function parameters. (That's true for this new case too.)
Sure Lisp has let and let* but the proposal here is NOT to provide arbitrary computational ability in the parameter list but to provide a way to have defaults that are not static. We shouldn't over-engineer things just because we can.
On Wed, May 13, 2009 at 10:41 PM, Bruce Leban <br...@leapyear.org> wrote:
> On Wed, May 13, 2009 at 5:10 PM, George Sakkis <george.sak...@gmail.com> > wrote:
>> On Wed, May 13, 2009 at 7:08 PM, Terry Reedy <tjre...@udel.edu> wrote:
>> > Bruce Leban wrote: >> >> def myfunc(a, b, c = *lambda: expression): >> >> stuff
>> > There is a proposal, which I thought was accepted in principle, to make >> > '* >> > seq' valid generally, not just in arg-lists. to mean 'unpack the >> > sequence'. >> > * (lambda:1,2)() would then be valid, and without the call, would be a >> > runtime, not syntax error.
>> Then how about putting the * before the parameter ?
>> def myfunc(a, b, *c = lambda: expression):
>> It's currently a syntax error, although the fact that "*arg" and >> "*arg=default" would mean something completely different is >> problematic. Still the same idea can be applied for some other >> operator (either valid already or not).
> I think similar syntax should do similar things. If *arg means one thing and > &arg means something else, that's confusing.
Confusing?? It's certainly no more confusing than using *args for one thing and **args for something else.
>> Subproposal (1): Get rid of the explicit lambda for dynamic arguments. >> That is,
>> def myfunc(a, b, *x=[]):
>> would be equivalent to what previous proposals would write as
>> def myfunc(a, b, *x=lambda: []):
> Explicit is better than implicit. There's a thunk getting created here, > right? Don't you want that to be obvious? I do.
Explicitness is in the eye of the beholder, and also an acquired taste. @decorator is less explicit than f = decorator(f) and yet it's generally considered a successful addition these days, despite the strong opposition it met initially.
>> Subproposal (2): If subproposal (1) is accepted, we could get for free >> (in terms of syntax at least) dynamic args depending on previous ones. >> That is,
>> def myfunc(a, b, *m=(a+b)/2):
>> would mean
>> def myfunc(a, b, *m = lambda a,b: (a+b)/2):
>> with the lambda being passed the values of a and b at runtime.
> It's not free and that adds quite a bit of complexity. Note that default > parameters are evaluated in the context of where the function is defined, > NOT in the middle of setting the other function parameters. (That's true for > this new case too.)
Sure, but I'm not sure what's your point here. The compiler can generate bytecode to the effect of:
def myfunc(a, b, *m = (a+b)/2): if m is not passed: m = default_m(a,b) # actual body follows
Ideally an optimizer would further check whether it's safe to inline the expression (which should be the typical case) to avoid the function call overhead.
> Sure Lisp has let and let* but the proposal here is NOT to provide arbitrary > computational ability in the parameter list but to provide a way to have > defaults that are not static. We shouldn't over-engineer things just because > we can.
Agreed, the primary target is to fix the common gotcha of mutable defaults, and I'd rather see this handled than nothing at all. A secondary goal that can be achieved here though is to reduce the overusage of None (or other sentinels for that matter). There are several articles to the effect of "Null considered harmful"; also SQL as well as some statically typed languages provide both nullable and non-nullable types and assume the latter by default. The only useful operation to a sentinel is identity check, so you know that every `x = sentinel` assignment should be followed by one or more `if x is sentinel` checks. Fewer nulls means potentially less conditional logic mental overhead. I'm not claiming we should get rid of None of course; there are legitimate reasons for different behavior under different conditions. Here however we're talking about a very specific pattern: def f(a, b=sentinel): if b is sentinel: b = <expression>
It may not seem such a big deal, but then again I don't think it's less trivial than the case for the (eventually) accepted ternary operator vs an if/else statement.
> spir wrote: > > My opinion on this is you're basically right. Even 'print' (for py<3.0) > > could be an identifier you could use in an assignment (or in any value > > expression), I guess, for parse patterns are different: print_statement : > > "print" expression assignment : name '=' expression So you can > > safely have "print" as name, or inside an expression. Even "print print" > > should work !
> But you would not want > print print > and > print(print) > to have two different meanings. > In Python, extra parens are fair around expressions, > and print(print) is clearly a function call.
I should have said "it's impossible short of looking at the source code or doing some very sophisticated introspection of the bytecode of the module the function is defined in".
Even so, your recipe doesn't quite work in several cases, aside from when the source code is not accessible. Two examples:
* The first one fails because default is not a global variable, thus not accessible from within the runtime decorator. I don't know how if this can be fixed. Note that for the function to exec() at all, you need to e.g. modify remove_decorators so that it also removes initial whitespace, something like:
def remove_decorators(source): """Removes the decorators from the given function""" lines = source.splitlines() lines = [line for line in lines if not line.startswith('@')] indent = 0 while lines[0][indent] == ' ': indent += 1 new_source = '\n'.join(line[indent:] for line in lines) return new_source
* The second one fails because of a clash of names. I guess that can be fixed by specifying what the locals and globals are explicitely in the calls to exec and eval.
> I should have said "it's impossible short of looking at the source > code or doing some very sophisticated introspection of the bytecode of > the module the function is defined in".
Any which way you slice this it will require that literal code *not* be interpreted until execution time. There are other ways to do that- storing it in strings, as George Sakkis does, modifying the language itself, as is the proposal here, or reading and parsing the original source. But you're right- more info is needed than what the bytecode contains.
> Even so, your recipe doesn't quite work in several cases, aside from > when the source code is not accessible.
Obviously, you are quite correct. Scoping in particular is difficult both to understand and to properly handle- had me chasing my tail for about twenty minutes earlier, actually- and I'm sure this is a security nightmare, but it does (generally) what is being asked for here. And it does so without recourse to changing the syntax.
Here's another possible mechanism:
def runtime(f): """Evaluates a function's annotations at runtime.""" annotations = getfullargspec(f)[-1] @wraps(f) def wrapped(*args, **kwargs): defaults = {k: eval(v) for k, v in annotations.items()} defaults.update(kwargs) return f(*args, **defaults) return wrapped
@runtime def example1(x, y:'[]'): y.append(x) return y
@runtime def example2(x:'a**2+2*b+c'): return x
Pretty simple, although it messes with the call syntax pretty badly, effectively treating a non-keyword argument as a keyword-only argument. There's probably a way around that but I doubt I'm going to see it tonight.
The point is, I don't really see the point in adding a new syntax. There are *lots* of incomplete solutions floating around to this issue, and it will probably take a lot less work to make one of those into a complete solution than it will to add a new syntax, if that makes any sense at all.
Also, do you mind posting any problems you find in that to the activestate message board so there is a record there?