Le Wed, 13 May 2009 20:10:42 -0400, George Sakkis <george.sak...@gmail.com> s'exprima ainsi:
> 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.
While I understand the intent, this seems complicated to me. I find clearer to express m in the func body; using a sentinel if m is a real default arg (meaning it could possibly be passed by the user).
UNDEF = object() def myfunc(a, b, m=UNDEF): if m is UNDEF: m = (a+b)/2)
Generally speaking, I find ok the need of sentinels for clarifying rare and non-obvious cases such as runtime-changing default values:
def somefunc(arg, m=UNDEF): if m is UNDEF: m = runtimeDefaultVal()
While I do not find ok the need of a sentinel to avoid the common gotcha of a default value beeing "back-updated" when the corresponding local var is changed in the func body:
def otherfunc(arg, l=UNDEF): if l is UNDEF: l = [] <possibly update l>
>> 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.
<gerald.brit...@gmail.com> wrote: > print(print) is not a function call in 2.x:
>>>> import types >>>> def f(): pass > ... >>>> isinstance(f, types.FunctionType) > True >>>> isinstance(print, types.FunctionType) > File "<stdin>", line 1 > isinstance(print, types.FunctionType) > ^ > SyntaxError: invalid syntax >>>> p = "hi there" >>>> print p > hi there >>>> print(p) > hi there
> (print_) is interpreted as an expression, which is then passed to the > print statement
> On Wed, May 13, 2009 at 3:30 PM, Scott David Daniels > <Scott.Dani...@acm.org> wrote: >> 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.
> Generally speaking, I find ok the need of sentinels for clarifying > rare and non-obvious cases such as runtime-changing default values:
> def somefunc(arg, m=UNDEF): > if m is UNDEF: > m = runtimeDefaultVal()
> While I do not find ok the need of a sentinel to avoid the common > gotcha of a default value beeing "back-updated" when the > corresponding local var is changed in the func body:
> def otherfunc(arg, l=UNDEF): > if l is UNDEF: > l = [] > <possibly update l>
But those two idioms are the same thing!
In the first case, if m is not provided by the caller, your function has to produce a fresh object at runtime. It does this by calling runtimeDefaultVal() which returns some unspecified object.
In the second case, if l is not provided by the caller, your function has to produce a fresh object at runtime. It does this by calling []. This is merely a special case of the first case, where runtimeDefaultVal() simply returns [] every time.
On Thu, 14 May 2009 12:41:17 pm Bruce Leban wrote:
> > 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.
No, I don't want it to be obvious. I don't care about thunks, I care that x gets bound at runtime. I don't care what the implementation is: whether it is a thunk, eval(), voodoo or something else, just so long as it works.
As for your argument that it is better to be explicit, when you want to add two numbers and compare them with a third, do you write:
(1 .__add__(1)).__eq__(2)
instead of
1+1 == 2? "Explicit is better than implicit", right?
No. 1+1=2 *is* explicit, because that's the Python syntax for addition. All those double-underscore method calls are implementation details that do not belong in "standard" Python code. If Python develops new syntax for late-binding of default arguments, that too will be explicit, and any reference to thunks (or any other mechanism) will be an implementation detail. The syntax shouldn't depend on the implementation.
lambda is already disliked by many people, including Guido. I don't think any suggestion that we make lambda more confusing by giving it two very different meanings ("create a thunk" inside function parameter lists, and "create a function" everywhere else) will be very popular on python-dev.
> The gode given is slow and ugly, but it does appear- > at least to me- to do what is being asked here.
Your code seems to work only if the source to the function is available. That will mean it can't be used by people who want to distribute .pyc files only.
> > The gode given is slow and ugly, but it does appear- > > at least to me- to do what is being asked here.
> Your code seems to work only if the source to the function is available. > That will mean it can't be used by people who want to distribute .pyc > files only.
> -- > Steven D'Aprano
I think the list is eating my replies, but suffice to say that there's a new version of the recipe at <URL: http://code.activestate.com/recipes/576754/> that doesn't have that limitation and looks pretty close to the syntax proposed above.
Example:
>>> @runtime
... def myfunc(x, y, z: lambda:[]): ... z.extend((x,y)) ... return z ...
> 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>'.
Some people don't like writing:
def f(x=SENTINEL): if x is SENTINEL: x = []
and wish to have syntax so they can write something approaching:
def f(x=[]): ...
but have a fresh [] bound to x. You're supporting the syntax:
@call_lambdas # Geremy Condra uses the name 'runtime' def f(x=lambda:[]): ...
(For the record, I've suggested creating a unary-& operator so that we can write "def f(&x=[])" to get late-binding of x.)
If I were to use the proposed late-binding feature, I would want it to be easy to use and obvious. I don't mind having to learn special syntax -- I'm not asking for it to be intuitive or guessable. But having to define the default value as a function (with or without lambda!) *and* call a decorator doesn't seem either easy or obvious. It feels like a kludge designed to get around a limitation of the language. (If you don't like the negative connotations of 'kludge', read it as 'hack' instead.) In other words, it looks like your suggestion is "let's find another idiom for late-binding default arguments" rather than "let's give Python built-in support for optional late-binding of default arguments".
If the first one is your intention, then I'll just walk away from this discussion. I already have a perfectly obvious and explicit idiom for late-binding of default arguments. I don't need a second one, especially one which I find exceedingly inelegant and ugly. If you want to use that in your own code, go right ahead, but I hope it never makes it into any code I ever need to read. -1 from me on any solution which requires both a decorator and special treatment of defaults in the parameter list.
In my opinion, only a solution with built-in support from the compiler is worth supporting. Anything else is a heavyweight, complicated solution for a problem that already has a lightweight, simple solution: use a sentinel. We already have a concise, fast, straightforward idiom which is easily learned and easily written, and while it's not intuitively obvious to newbies, neither is the suggested decorator+lambda solution. We don't need a complicated, verbose, hard-to-explain, hard-to-implement solution as well.
> On May 14, 6:27 pm, Steven D'Aprano <st...@pearwood.info> wrote: > > On Thu, 14 May 2009 09:03:34 am CTO wrote: > > > 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/>.
> > [...]
> > > The gode given is slow and ugly, but it does appear- > > > at least to me- to do what is being asked here.
> > Your code seems to work only if the source to the function is > > available. That will mean it can't be used by people who want to > > distribute .pyc files only.
> > -- > > Steven D'Aprano
> I think the list is eating my replies, but suffice to say that > there's a new version of the recipe at <URL: > http://code.activestate.com/recipes/576754/> that doesn't have that > limitation and looks pretty close to the syntax proposed above.
And instead has another limitation, namely that it only works if you pass the non-default argument by keyword.
f(123, y=456) # works f(123, 456) # fails if y has been given a default value.
> and wish to have syntax so they can write something approaching:
> def f(x=[]): > ...
And I understand that. However, I don't think it's important enough to make it worth changing the language, adding to Python's already significant function call overhead, or making the job of parsing function signatures more difficult. If there is a mechanism to do this inside of Python- and there are several- it is my personal opinion that those should be used in preference to modifying the language. As I am neither the smartest nor most competent programmer here, feel free to disregard my opinion- but the code I have produced matches one of the proposed syntaxes very closely, even if it is not the one you prefer.
> but have a fresh [] bound to x. You're supporting the syntax:
> @call_lambdas # Geremy Condra uses the name 'runtime' > def f(x=lambda:[]): > ...
For the record, I'm not supporting a syntax. I'm simply stating that this can be done in Python as it currently stands, and that I am most emphatically not in favor of making function signatures any more complex than they already are.
> (For the record, I've suggested creating a unary-& operator so that we > can write "def f(&x=[])" to get late-binding of x.)
It's simple, short, and concise. If I were to get behind a proposal to change the language to support this feature, I would probably either get behind this one or perhaps a more general system for adding a metaclass equivalent to functions. However, as things stand I remain unconvinced that any of these things are necessary, or even particularly desirable, given the aforementioned complexity of function signatures.
> If I were to use the proposed late-binding feature, I would want it to > be easy to use and obvious. I don't mind having to learn special > syntax -- I'm not asking for it to be intuitive or guessable. But > having to define the default value as a function (with or without > lambda!) *and* call a decorator doesn't seem either easy or obvious. It > feels like a kludge designed to get around a limitation of the > language. (If you don't like the negative connotations of 'kludge', > read it as 'hack' instead.) In other words, it looks like your > suggestion is "let's find another idiom for late-binding default > arguments" rather than "let's give Python built-in support for optional > late-binding of default arguments".
My suggestion is neither to find another idiom or to build in late-binding support. Some people- yourself included- want a new syntax. I demonstrated that close approximations of some of the mentioned syntaxes were possible in the language already, and while I appreciate that your preferred syntax is not on that list, I remain unconvinced that its purported benefits outweigh what I perceive to be its drawbacks.
> If the first one is your intention, then I'll just walk away from this > discussion. I already have a perfectly obvious and explicit idiom for > late-binding of default arguments. I don't need a second one, > especially one which I find exceedingly inelegant and ugly. If you want > to use that in your own code, go right ahead, but I hope it never makes > it into any code I ever need to read. -1 from me on any solution which > requires both a decorator and special treatment of defaults in the > parameter list.
If you are satisfied with the existing idiom, then use it. If you're not, my code is out there. If you don't like that, then write your own.
> In my opinion, only a solution with built-in support from the compiler > is worth supporting.
I'm afraid I'm unconvinced on that point.
> Anything else is a heavyweight, complicated > solution for a problem that already has a lightweight, simple solution: > use a sentinel. We already have a concise, fast, straightforward idiom > which is easily learned and easily written, and while it's not > intuitively obvious to newbies, neither is the suggested > decorator+lambda solution. We don't need a complicated, verbose, > hard-to-explain, hard-to-implement solution as well.
> -- > Steven D'Aprano
I think I've already addressed this point, but once more for the record, I'm just not convinced that any of this- my code or your proposed changes- are needed. Until then you can have my -1.
> > On May 14, 6:27 pm, Steven D'Aprano <st...@pearwood.info> wrote: > > > On Thu, 14 May 2009 09:03:34 am CTO wrote: > > > > 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/>.
> > > [...]
> > > > The gode given is slow and ugly, but it does appear- > > > > at least to me- to do what is being asked here.
> > > Your code seems to work only if the source to the function is > > > available. That will mean it can't be used by people who want to > > > distribute .pyc files only.
> > > -- > > > Steven D'Aprano
> > I think the list is eating my replies, but suffice to say that > > there's a new version of the recipe at <URL: > >http://code.activestate.com/recipes/576754/> that doesn't have that > > limitation and looks pretty close to the syntax proposed above.
> And instead has another limitation, namely that it only works if you > pass the non-default argument by keyword.
> f(123, y=456) # works > f(123, 456) # fails if y has been given a default value.
> -- > Steven D'Aprano
Correct. However, I remain confident that someone with ever so slightly more skill than myself can correct that problem- since you already seem to have taken a look at it, maybe that's something you could do? Thanks in advance,
> On Thu, 14 May 2009 12:41:17 pm Bruce Leban wrote:
> > Explicit is better than implicit. There's a thunk getting created > > here, right? Don't you want that to be obvious? I do.
> No, I don't want it to be obvious. I don't care about thunks, I care > that x gets bound at runtime. I don't care what the implementation is: > whether it is a thunk, eval(), voodoo or something else, just so long > as it works.
> As for your argument that it is better to be explicit, when you want to > add two numbers and compare them with a third, do you write:
> (1 .__add__(1)).__eq__(2)
> instead of
> 1+1 == 2?
Absolutely not. This is a false analogy. The anology would be having an implicit multiplication operator and writing (a b c) instead of (a * b * c).
<snip> The syntax shouldn't depend on the
> implementation.
> lambda is already disliked by many people, including Guido. I don't > think any suggestion that we make lambda more confusing by giving it > two very different meanings ("create a thunk" inside function parameter > lists, and "create a function" everywhere else) will be very popular on > python-dev.
> I'm *not* suggesting a new meaning for lambda! This is the same meaning
that it has right now. The new meaning is the decorator attached to the default assignment that says evaluate that lambda.
I'll use an @dynamic decorator-like syntax to illustrate. These would be valid:
def foo(a, b = @dynamic lambda: []): def foo(a, b = @dynamic lambda: list()): def foo(a, b = @dynamic list): def foo(a, b = @dynamic random.random):
and this would not:
def foo(a, b = @dynamic []) def foo(a, b = @dynamic 5)
because @dynamic says that the thing that follows is called to generate a dynamic default parameter value and you can't call [] or 5.
My point about creating a thunk is *not* an implementation detail. The point here is that if you use one of the forms above with a lambda, it's the lambda creating a thunk/closure/voodoo thing at this point in the program, *not* the @dynamic decorator. The scope of that lambda is exactly what it looks like it is with or without the @dynamic decorator. Likewise, in the random.random, example, it's the value of random.random at the time the function is defined, not some later value that might be assigned to that name.
If you use some other syntax that doesn't look like a lambda, I have to learn the scoping rules for that syntax. I already know the rules for lambda.
> > def f(x=SENTINEL): > > if x is SENTINEL: x = []
> > and wish to have syntax so they can write something approaching:
> > def f(x=[]): > > ...
> And I understand that. However, I don't think it's important > enough to make it worth changing the language, adding to > Python's already significant function call overhead, or > making the job of parsing function signatures more difficult. > If there is a mechanism to do this inside of Python- and > there are several- it is my personal opinion that those > should be used in preference to modifying the language. As > I am neither the smartest nor most competent programmer > here, feel free to disregard my opinion- but the code I > have produced matches one of the proposed syntaxes very > closely, even if it is not the one you prefer.
Your code also "add[s] to Python's already significant function call overhead" as well as "making the job of parsing function signatures more difficult".
I don't mean to dump on your code. What you are trying to do is obviously very difficult from pure Python code, and the solutions you have come up with are neat kludges. But a kludge is still a kludge, no matter how neat it is :)
[...]
> > (For the record, I've suggested creating a unary-& operator so that > > we can write "def f(&x=[])" to get late-binding of x.)
> It's simple, short, and concise. If I were to get behind > a proposal to change the language to support this feature, > I would probably either get behind this one or perhaps > a more general system for adding a metaclass equivalent > to functions. However, as things stand I remain unconvinced > that any of these things are necessary, or even > particularly desirable, given the aforementioned > complexity of function signatures.
I think we two at least agree. I don't think there's anything wrong with the current sentinel idiom. It's not entirely intuitive to newbies, or those who don't fully understand Python's object-binding model, but I don't consider that a flaw. So I don't see the compile-time binding of default args to be a problem that needs solving.
But other people do, and they are loud and consistent in their complaints. Given that the squeaky wheel (sometimes) gets the grease, I'd just like to see a nice solution to a (non-)problem rather than an ugly solution.
So I'm +0 on my proposal -- I don't think it solves a problem that needs solving, but other people do. I'm -1 on decorator+lambda solutions, because not only do they not solve a problem that needs solving, but they don't solve it in a particularly ugly and inefficient way *wink*
> My suggestion is neither to find another idiom or to build in > late-binding support. Some people- yourself included-
I think you've misunderstood my position. I'm one of the people defending the current semantics of default arg binding. But since others want optional late binding, I'm just trying to find a syntax that doesn't bite :)
> want a > new syntax. I demonstrated that close approximations of some > of the mentioned syntaxes were possible in the language already, > and while I appreciate that your preferred syntax is not on that > list, I remain unconvinced that its purported benefits outweigh > what I perceive to be its drawbacks.
Just out of curiosity, what do you see as the drawbacks? The ones that come to my mind are:
* people who want late binding to be standard will be disappointed (but that will be true of any solution)
* requires changes to Python's parser, to allow unary-& (but that will probably be very simple)
* requires charges to Python's compiler, to allow for some sort of late-binding semantics (thunks?) (but that will probably be very hard)
* requires people to learn one more feature (so newbies will still be confused that def f(x=[]) doesn't behave as they expect).
Any chance we could have the interpreter raise a warning for the case
def foo(a = []): #stuff
?
The empty list and empty dict args would, I imagine, be the two most common mistakes. Showing a warning might, at least, solve the problem of people tripping over the syntax.
I think this takes the discussion in a more practical direction. Imagine that there were a special method name __immutable__ to be implemented appropriately by all builtin types. Any object passed as a default argument would be checked to see that its type implements __immutable__ and that __immutable__() is True. Failure would mean a warning or even an error in subsequent versions.
User-defined types could implement __immutable__ as they saw fit, in the traditional Pythonic consenting-adults-ly way.
On Thu, May 14, 2009 at 9:16 PM, Tennessee Leeuwenburg <
tleeuwenb...@gmail.com> wrote: > A thought from another direction...
> Any chance we could have the interpreter raise a warning for the case
> def foo(a = []): > #stuff
> ?
> The empty list and empty dict args would, I imagine, be the two most common > mistakes. Showing a warning might, at least, solve the problem of people > tripping over the syntax.
> Just out of curiosity, what do you see as the drawbacks?
[snip]
1) It adds to the complexity (and therefore overhead) of calling functions- not just the functions which use it, but even functions which operate as normal. Python already has a hefty penalty for calling functions, and I really don't want it to get any heavier. My 'solutions', as incomplete as they are, at least don't slow down anything else. 2) It adds to the complexity of introspecting functions. Take a good look at inspect.getfullargspec- its a nightmare, and either it gets worse under this (bad) or it doesn't include information that is available to the compiler (even worse).
In addition to those minuses, it doesn't actually add to the capabilities of the language. If this were a proposal to add early action to Python (the equivalent of metaclasses or, to some extent, macro replacement) I would be much more likely to support it, despite the heavier syntax.
So, the existing idiom works pretty well, there doesn't seem to be a very good substitute, it slows the whole language down to implement, and it doesn't add any power if you do.
On Thu, May 14, 2009 at 9:16 PM, Tennessee Leeuwenburg
<tleeuwenb...@gmail.com> wrote: > A thought from another direction...
> Any chance we could have the interpreter raise a warning for the case
> def foo(a = []): > #stuff
> ?
> The empty list and empty dict args would, I imagine, be the two most common > mistakes. Showing a warning might, at least, solve the problem of people > tripping over the syntax.
+1 on throwing a ValueError for non-hash()-able (and thus probably mutable) default argument values. It's by no means perfect since objects are hash()-able by default using their ID, but it would at least help in the frequent "well-behaved mutable container object" cases. The barrier to this idea would be the code breakage involved; IMHO, code exploiting mutable defaults as static variables is in poor style anyway, but backward compatibility is a significant concern of the BDFL and Python devs; though I would hope the breakage might be seen as justifiable in this case.
> On Thu, May 14, 2009 at 9:16 PM, Tennessee Leeuwenburg > <tleeuwenb...@gmail.com> wrote:
>> A thought from another direction...
>> Any chance we could have the interpreter raise a warning for the case
>> def foo(a = []): >> #stuff
>> ?
>> The empty list and empty dict args would, I imagine, be the two most >> common mistakes. Showing a warning might, at least, solve the problem of >> people tripping over the syntax. On Thu, May 14, 2009 at 9:31 PM, Curt Hagenlocher <c...@hagenlocher.org> wrote: > I think this takes the discussion in a more practical direction. Imagine > that there were a special method name __immutable__ to be implemented > appropriately by all builtin types. Any object passed as a default argument > would be checked to see that its type implements __immutable__ and that > __immutable__() is True. Failure would mean a warning or even an error in > subsequent versions.
> User-defined types could implement __immutable__ as they saw fit, in the > traditional Pythonic consenting-adults-ly way.
(A) Python's new Abstract Base Classes would probably be a better way of doing such checking rather than introducing a new special method
(B) What about having an __immutable__() that returned an immutable version of the object if possible? Then all default arguments could be converted to immutables at definition-time, with errors if a default cannot be made immutable? It would eliminate the performance concerns since the overhead would only be incurred once (when the function gets defined), rather than with each function call.
Bruce Leban wrote: > I'll use an @dynamic decorator-like syntax to illustrate. These would be > valid:
> def foo(a, b = @dynamic lambda: []): > def foo(a, b = @dynamic lambda: list()): > def foo(a, b = @dynamic list): > def foo(a, b = @dynamic random.random):
> and this would not:
> def foo(a, b = @dynamic []) > def foo(a, b = @dynamic 5)
> because @dynamic says that the thing that follows is called to generate a > dynamic default parameter value and you can't call [] or 5.
Hmm, very interesting, but in your example what is "dynamic" doing? Are you proposing it as a keyword to signal "here comes a dynamic default"? Do we really need it? Why not something like this:
def five_appender(x=@list): x.append(5) return x
>>> five_appender() [5] >>> five_appender()
[5]
The idea is that @ is a magic sigil meaning, "call this if no argument is passed in." So, as per your prior example @[] or @5 would be result in a runtime error, since they're not callable. If for some reason you want a fresh 5 (I can't think of why, since it's immutable, but whatever), you would need to use a lambda:
def n_appender(n=@lambda: 5, x=@list): x.append(n) return x
Do y'all think this is enough inline with how @ is already used to make sense? Or is it too different from the existing use of @?
> Bruce Leban wrote: > > I'll use an @dynamic decorator-like syntax to illustrate. These would be > > valid:
> > def foo(a, b = @dynamic lambda: []): > > def foo(a, b = @dynamic lambda: list()): > > def foo(a, b = @dynamic list): > > def foo(a, b = @dynamic random.random):
> > and this would not:
> > def foo(a, b = @dynamic []) > > def foo(a, b = @dynamic 5)
> > because @dynamic says that the thing that follows is called to generate a > > dynamic default parameter value and you can't call [] or 5.
> Hmm, very interesting, but in your example what is "dynamic" doing? > Are you proposing it as a keyword to signal "here comes a dynamic > default"? Do we really need it? Why not something like this:
> def five_appender(x=@list): > x.append(5) > return x
> >>> five_appender() > [5] > >>> five_appender()
> [5]
> The idea is that @ is a magic sigil meaning, "call this if no argument > is passed in." So, as per your prior example @[] or @5 would be result > in a runtime error, since they're not callable. If for some reason you > want a fresh 5 (I can't think of why, since it's immutable, but > whatever), you would need to use a lambda:
> def n_appender(n=@lambda: 5, x=@list): > x.append(n) > return x
> Do y'all think this is enough inline with how @ is already used to > make sense? Or is it too different from the existing use of @?
> -- Carl Johnson
If we're making magical objects, why not just make a magical object that gives you the ability to defer the execution of a block of code until an operation is performed on it? That way at least it makes sense if you've learned the rest of the language.
> > Just out of curiosity, what do you see as the drawbacks?
> [snip]
> 1) It adds to the complexity (and therefore overhead) of > calling functions- not just the functions which use it, > but even functions which operate as normal.
Without an implementation, how can you possibly predict the cost of it?
> Python > already has a hefty penalty for calling functions,
I think you're badly mistaken. Python has a hefty cost for looking up names, but the overhead to *call* a function once you have looked up the name is minimal.
> and > I really don't want it to get any heavier. My 'solutions', > as incomplete as they are, at least don't slow down > anything else.
Oh the irony. Decorators are very heavyweight. Here's a decorator that essentially does nothing at all, and it triples the cost of calling the function:
>>> from functools import wraps >>> def decorator(f):
I think, before making claims as to what's costly and what isn't, you should actually do some timing measurements.
> 2) It adds to the complexity of introspecting functions. > Take a good look at inspect.getfullargspec- its a > nightmare, and either it gets worse under this (bad) > or it doesn't include information that is available > to the compiler (even worse).
Well obviously this is going to make getfullargspec more complicated. But tell me, what do you think your solution using decorators does to getfullargspec?
> In addition to those minuses, it doesn't actually add > to the capabilities of the language.
It's an incremental improvement. Currently, late-binding of defaults requires boilerplate code. This will eliminate that boilerplate code.
> If this were a > proposal to add early action to Python (the equivalent > of metaclasses or, to some extent, macro replacement) > I would be much more likely to support it, despite the > heavier syntax.
> So, the existing idiom works pretty well,
100% agreed!
> there doesn't seem to be a very good substitute,
Not without support in the compiler.
> it slows the whole language down to implement,
You can't know that.
> and it doesn't add any power if you do.
It reduces boilerplate, which is a good thing. Probably the *only* good thing, but still a good thing.
> > > Just out of curiosity, what do you see as the drawbacks?
> > [snip]
> > 1) It adds to the complexity (and therefore overhead) of > > calling functions- not just the functions which use it, > > but even functions which operate as normal.
> Without an implementation, how can you possibly predict the cost of it?
> > Python > > already has a hefty penalty for calling functions,
> I think you're badly mistaken. Python has a hefty cost for looking up > names, but the overhead to *call* a function once you have looked up > the name is minimal.
> > and > > I really don't want it to get any heavier. My 'solutions', > > as incomplete as they are, at least don't slow down > > anything else.
> Oh the irony. Decorators are very heavyweight. Here's a decorator that > essentially does nothing at all, and it triples the cost of calling the > function:
> >>> from functools import wraps > >>> def decorator(f):
> I think, before making claims as to what's costly and what isn't, you > should actually do some timing measurements.
> > 2) It adds to the complexity of introspecting functions. > > Take a good look at inspect.getfullargspec- its a > > nightmare, and either it gets worse under this (bad) > > or it doesn't include information that is available > > to the compiler (even worse).
> Well obviously this is going to make getfullargspec more complicated. > But tell me, what do you think your solution using decorators does to > getfullargspec?
> > In addition to those minuses, it doesn't actually add > > to the capabilities of the language.
> It's an incremental improvement. Currently, late-binding of defaults > requires boilerplate code. This will eliminate that boilerplate code.
> > If this were a > > proposal to add early action to Python (the equivalent > > of metaclasses or, to some extent, macro replacement) > > I would be much more likely to support it, despite the > > heavier syntax.
> > So, the existing idiom works pretty well,
> 100% agreed!
> > there doesn't seem to be a very good substitute,
> Not without support in the compiler.
> > it slows the whole language down to implement,
> You can't know that.
> > and it doesn't add any power if you do.
> It reduces boilerplate, which is a good thing. Probably the *only* good > thing, but still a good thing.
On Fri, 15 May 2009 03:08:18 pm Stephen J. Turnbull wrote:
> Could you summarize that discussion briefly?
Many newbies, and some more experienced programmers, are confused by the behaviour of functions when parameters are given default mutable arguments:
>>> def f(x=[]):
... x.append(1) ... return x ...
>>> f() [1] >>> f()
[1, 1]
Some people are surprised by this behaviour, and would prefer that the default value for x be freshly created each time it is needed. This is one of the most common, and most acrimonious, topics of discussion on comp.lang.python. The standard idiom for the expected behaviour is to insert boilerplate code that checks for a sentinel:
def f(x=None): if x is None: x = [] x.append(1) return x
The chances of having the standard behaviour changed are slim, at best, for various reasons including backward compatibility and runtime efficiency. Also, I believe Guido has ruled that the standard behaviour will not be changed.
However, some have suggested that if the standard compile-time creation of defaults won't be changed, perhaps it could be made optional, with special syntax, or perhaps a decorator, controlling the behaviour. See these two proof-of-concept decorators, by Geremy Condra, for example:
I'm not convinced by decorator-based solutions, so I'll pass over them. I assume that any first-class solution will require cooperation from the compiler, and thus move the boilerplate out of the function body into the byte code. (Or whatever implementation is used -- others have suggested using thunks.)
Assuming such compiler support is possible, it only remains to decide on syntax for it. Most suggested syntax I've seen has marked the default value itself, e.g.: def f(x = new []). Some have suggested overloading lambda, perhaps with some variation like def f(x = *lambda:[]).
I suggest that the markup should go on the formal parameter name, not the default value: we're marking the formal parameter as "special" for using delayed semantics, not that the default object (usually [] or {}) will be special.
Some years ago, Python overloaded the binary operators * and ** for use as special markers in parameter lists. I suggest we could do the same, by overloading the & operator in a similar fashion: inside the parameter list, &x would mean to delay evaluation of the default argument:
def f(x=[], &y=[])
x would use the current compile-time semantics, y would get the new runtime semantics.
I don't have any particular reason for choosing & over any other binary operator. I think ^ would also be a good choice.
Tagging a parameter with unary-& but failing to specify a default value should be a syntax error. Likewise for unary-& outside of a parameter list. (At least until such time as somebody suggests a good use for such a thing.)