Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Python > what does 'a=b=c=[]' do

Reply
Thread Tools

what does 'a=b=c=[]' do

 
 
Steven D'Aprano
Guest
Posts: n/a
 
      12-23-2011
On Fri, 23 Dec 2011 13:13:38 +0000, Neil Cerutti wrote:

> On 2011-12-23, Neil Cerutti <(E-Mail Removed)> wrote:
>> Is the misfeature that Python doesn't evaluate the default argument
>> expression every time you call the function? What would be the harm if
>> it did?

>
> ...you know, assuming it wouldn't break existing code.


It will. Python's default argument strategy has been in use for 20 years.
Some code will rely on it. I know mine does.

There are two strategies for dealing with default arguments that I know
of: early binding and late binding. Python has early binding: the default
argument is evaluated once, when the function is created. Late binding
means the default argument is always re-evaluated each time it is needed.

Both strategies are reasonable choices. Both have advantages and
disadvantages. Both have use-cases, and both lead to confusion when the
user expects one but gets the other. If you think changing from early to
late binding will completely eliminate the default argument "gotcha", you
haven't thought things through -- at best you might reduce the number of
complaints, but only at the cost of shifting them from one set of use-
cases to another.

Early binding is simple to implement and simple to explain: when you
define a function, the default value is evaluated once, and the result
stored to be used whenever it is needed. The disadvantage is that it can
lead to unexpected results for mutable arguments.

Late binding is also simple to explain, but a little harder to implement.
The function needs to store the default value as a piece of code (an
expression) which can be re-evaluated as often as needed, not an object.

The disadvantage of late binding is that since the expression is live, it
needs to be calculated each time, even if it turns out to be the same
result. But there's no guarantee that it will return the same result each
time: consider a default value like x=time.time(), which will return a
different value each time it is called; or one like x=a+b, which will
vary if either a or b are changed. Or will fail altogether if either a or
b are deleted. This will surprise some people some of the time and lead
to demands that Python "fix" the "obviously buggy" default argument
gotcha.

If a language only offers one, I maintain it should offer early binding
(the status quo). Why? Because it is more elegant to fake late binding in
an early binding language than vice versa.

To fake late binding in a language with early binding, use a sentinel
value and put the default value inside the body of the function:

def func(x, y=None):
if y is None:
y = []
...

All the important parts of the function are in one place, namely inside
the function.

To fake early binding when the language provides late binding, you still
use a sentinel value, but the initialization code creating the default
value is outside the body of the function, usually in a global variable:

_DEFAULT_Y = [] # Private constant, don't touch.

def func(x, y=None):
if y is None:
y = _DEFAULT_Y
...

This separates parts of the code that should be together, and relies on a
global, with all the disadvantages that implies.



--
Steven
 
Reply With Quote
 
 
 
 
Chris Angelico
Guest
Posts: n/a
 
      12-23-2011
On Sat, Dec 24, 2011 at 2:49 AM, Steven D'Aprano
<(E-Mail Removed)> wrote:
> To fake early binding when the language provides late binding, you still
> use a sentinel value, but the initialization code creating the default
> value is outside the body of the function, usually in a global variable:
>
> * *_DEFAULT_Y = [] *# Private constant, don't touch.
>
> * *def func(x, y=None):
> * * * *if y is None:
> * * * * * *y = _DEFAULT_Y
> * * * *...
>
> This separates parts of the code that should be together, and relies on a
> global, with all the disadvantages that implies.


A static variable (in the C sense) would make this just as clean as
the alternative. In Python, that could be implemented as an attribute
of the function object. Oh looky here... that's how default arguments
are implemented.

Tim Toady.

ChrisA
 
Reply With Quote
 
 
 
 
rusi
Guest
Posts: n/a
 
      12-23-2011
On Dec 23, 8:33*pm, Steven D'Aprano <steve
(E-Mail Removed)> wrote:
> On Fri, 23 Dec 2011 06:57:02 -0800, rusi wrote:
> > On Dec 23, 6:53*pm, Robert Kern <(E-Mail Removed)> wrote:
> >> On 12/23/11 1:23 PM, rusi wrote:

> [...]
> >> > Of course it should be fixed. *The repeated recurrence of it as a
> >> > standard gotcha as well as the python ideas list testifies to that.

>
> >> So you were lying when you said that you did not ask or imply that it
> >> should be 'fixed'? Please make up your mind. It's quite rude to
> >> "defend" your position by constantly shifting it whenever you get
> >> challenged on it.

>
> > Meanings of "should"http://www.englishpage.com/modals/should.htmlMy
> > first should was the 3rd one
> > My second was the 1st one.

>
> Rusi, are you a native English speaker? Because that excuse/defense does
> not excuse your comments at all, multiple meanings of "should" or not.
> Why not just admit to a mistake? After denying you were asking for Python
> to fix a so-called bug, you then called for Python to fix it. If this
> were a real argument rather than a friendly debate, people would be
> crying "Gotcha!" for sure.


Ok You got me!

Does that help the OP's?

> It's a slick language but I still have trouble wrapping my brain around some of the concepts.


 
Reply With Quote
 
Roy Smith
Guest
Posts: n/a
 
      12-23-2011
In article <4ef4a30d$0$29973$c3e8da3$(E-Mail Removed) om>,
Steven D'Aprano <(E-Mail Removed)> wrote:

> The disadvantage of late binding is that since the expression is live, it
> needs to be calculated each time, even if it turns out to be the same
> result. But there's no guarantee that it will return the same result each
> time: consider a default value like x=time.time(), which will return a
> different value each time it is called


I know this is not quite the same thing, but it's interesting to look at
what django (and mongoengine) do in their model definitions, prompted by
your time.time() example. You can do declare a model field something
like:

class Foo(models.Model):
timestamp = DateTimeField(default=datetime.utcnow)

Now, when you create a Foo, if you don't supply a timestamp, it
generates one by calling utcnow() for you. Very handy.

I'm not arguing that Python should do that, just pointing out an example
of where late binding is nice. Technically, that's probably not really
late binding. You always get the same function bound, it's just called
each time it's used because django notices that you passed a callable.
Maybe sort of "early binding, late calling" would be a more apt
description, but given that python has descriptors, the line between
data and function sometimes gets a little blurry anyway.
 
Reply With Quote
 
Mel Wilson
Guest
Posts: n/a
 
      12-23-2011
Steven D'Aprano wrote:
> On Fri, 23 Dec 2011 13:13:38 +0000, Neil Cerutti wrote:
>> On 2011-12-23, Neil Cerutti <(E-Mail Removed)> wrote:
>> ...you know, assuming it wouldn't break existing code.

>
> It will. Python's default argument strategy has been in use for 20 years.
> Some code will rely on it. I know mine does.


In a tool that's meant for other people to use to accomplish work of their
own, breaking workflow is a cardinal sin.

In a research language that's meant always to be up-to-date with the concept
of the week, not so much.

Mel.

 
Reply With Quote
 
Neil Cerutti
Guest
Posts: n/a
 
      12-23-2011
On 2011-12-23, Steven D'Aprano <(E-Mail Removed)> wrote:
> On Fri, 23 Dec 2011 13:13:38 +0000, Neil Cerutti wrote:
>> On 2011-12-23, Neil Cerutti <(E-Mail Removed)> wrote:
>>> Is the misfeature that Python doesn't evaluate the default
>>> argument expression every time you call the function? What
>>> would be the harm if it did?

>>
>> ...you know, assuming it wouldn't break existing code.

>
> It will. Python's default argument strategy has been in use for
> 20 years. Some code will rely on it. I know mine does.


I'm aware of that. I should have put the question differently,
but you did guess what I meant to ask. Thanks for the dicussion.

> Early binding is simple to implement and simple to explain:
> when you define a function, the default value is evaluated
> once, and the result stored to be used whenever it is needed.
> The disadvantage is that it can lead to unexpected results for
> mutable arguments.
>
> Late binding is also simple to explain, but a little harder to
> implement. The function needs to store the default value as a
> piece of code (an expression) which can be re-evaluated as
> often as needed, not an object.
>
> The disadvantage of late binding is that since the expression
> is live, it needs to be calculated each time, even if it turns
> out to be the same result. But there's no guarantee that it
> will return the same result each time:


That's its main *advantage*.

> consider a default value like x=time.time(), which will return
> a different value each time it is called; or one like x=a+b,
> which will vary if either a or b are changed. Or will fail
> altogether if either a or b are deleted. This will surprise
> some people some of the time and lead to demands that Python
> "fix" the "obviously buggy" default argument gotcha.


It's hard to see anyone being confused by the resultant
exception. It's much harder to figure out what's going wrong with
an early-bound mutable.

> If a language only offers one, I maintain it should offer early
> binding (the status quo). Why? Because it is more elegant to
> fake late binding in an early binding language than vice versa.
>
> To fake late binding in a language with early binding, use a
> sentinel value and put the default value inside the body of the
> function:
>
> def func(x, y=None):
> if y is None:
> y = []
> ...
>
> All the important parts of the function are in one place,
> namely inside the function.
>
> To fake early binding when the language provides late binding,
> you still use a sentinel value, but the initialization code
> creating the default value is outside the body of the function,
> usually in a global variable:
>
> _DEFAULT_Y = [] # Private constant, don't touch.
>
> def func(x, y=None):
> if y is None:
> y = _DEFAULT_Y
> ...
>
> This separates parts of the code that should be together, and
> relies on a global, with all the disadvantages that implies.


I'd use a function attribute.

def func(x, y=None):
if y is None:
y = func.default_y
...
func.default_y = []

That's awkward only if you believe function attributes are
awkward. Even if common practice were instead to use a global
variable, as you did, it wouldn't be considered bad if it were a
common idiom. In case, a global that's not meant to be rebound or
mutated is the best possible variety.

However, I also wouldn't base the decision of late versus early
binding on how confusing it would be if you assume wrongly which
one you are getting. Most default function arguments are
literals, so in Python the question just doesn't come up until
you get mutabled.

The greater efficiency was probably what decided this question
for Python, right? Since late-binding is so easy to fake, is
hardly ever what you want, and would make all code slower, why do
it?

--
Neil Cerutti
 
Reply With Quote
 
Michael Torrie
Guest
Posts: n/a
 
      12-23-2011
On 12/23/2011 03:31 AM, rusi wrote:
> In Fortran, if the comma in the loop
> DO 10 I = 1,10
> is misspelt as '.' it becomes the assignment
> DO10I = 1.0
>
> Do you consider it a bug or a feature?
> Does Fortran consider it a bug or feature?


Non sequitor. Nothing at all to do with the issue at hand.
Furthermore, you are talking about a parser "feature" not a runtime
behavior.
 
Reply With Quote
 
Steven D'Aprano
Guest
Posts: n/a
 
      12-23-2011
On Sat, 24 Dec 2011 02:55:41 +1100, Chris Angelico wrote:

> On Sat, Dec 24, 2011 at 2:49 AM, Steven D'Aprano
> <(E-Mail Removed)> wrote:
>> To fake early binding when the language provides late binding, you
>> still use a sentinel value, but the initialization code creating the
>> default value is outside the body of the function, usually in a global
>> variable:
>>
>> * *_DEFAULT_Y = [] *# Private constant, don't touch.
>>
>> * *def func(x, y=None):
>> * * * *if y is None:
>> * * * * * *y = _DEFAULT_Y
>> * * * *...
>>
>> This separates parts of the code that should be together, and relies on
>> a global, with all the disadvantages that implies.

>
> A static variable (in the C sense) would make this just as clean as the
> alternative. In Python, that could be implemented as an attribute of the
> function object. Oh looky here... that's how default arguments are
> implemented.


Yes. But having to manage it *by hand* is still unclean:

* you still have to assign the default value to the function assignment
outside the function, which is inelegant;

* if you rename the function, the internal lookup func.default_value will
fail.

I'm not saying it can't be done. I'm just saying that the status quo is
cleaner and more elegant, and if you want late binding, there is a
simple, obvious, elegant idiom to get it.



--
Steven
 
Reply With Quote
 
Chris Angelico
Guest
Posts: n/a
 
      12-23-2011
On Sat, Dec 24, 2011 at 9:32 AM, Steven D'Aprano
<(E-Mail Removed)> wrote:
> Yes. But having to manage it *by hand* is still unclean:


Well, my point was that Python's current behaviour _is_ that.

> * you still have to assign the default value to the function assignment
> outside the function, which is inelegant;


C's static variables are initialized inside the function. But since
Python doesn't have that, it doesn't really work that way. (You might
be able to use a decorator to do some cool tricks though.)

> * if you rename the function, the internal lookup func.default_value will
> fail.


Python would benefit some from a "current-function" reference, if
tricks like this were to be encouraged. It'd help with recursion too.
hmmmm...

>>> def Y(func):

return lambda *args,**kwargs: func(func,*args,**kwargs)

>>> @Y

def foo(me,x):
if x>5: return x
return me(me,x+1),7,x

>>> foo(3)

(((6, 7, 5), 7, 4), 7, 3)

Useful? Not very. Maybe as a language feature, but not as a parameter.
But of curiosity value.

ChrisA
 
Reply With Quote
 
Devin Jeanpierre
Guest
Posts: n/a
 
      12-24-2011
> To fake early binding when the language provides late binding, you still
> use a sentinel value, but the initialization code creating the default
> value is outside the body of the function, usually in a global variable:
>
> _DEFAULT_Y = [] # Private constant, don't touch.
>
> def func(x, y=None):
> if y is None:
> y = _DEFAULT_Y
> ...
>
> This separates parts of the code that should be together, and relies on a
> global, with all the disadvantages that implies.


No, you can just do def func(x, y=_DEFAULT_Y): ...

-- Devin

On Fri, Dec 23, 2011 at 10:49 AM, Steven D'Aprano
<(E-Mail Removed)> wrote:
> On Fri, 23 Dec 2011 13:13:38 +0000, Neil Cerutti wrote:
>
>> On 2011-12-23, Neil Cerutti <(E-Mail Removed)> wrote:
>>> Is the misfeature that Python doesn't evaluate the default argument
>>> expression every time you call the function? What would be the harm if
>>> it did?

>>
>> ...you know, assuming it wouldn't break existing code.

>
> It will. Python's default argument strategy has been in use for 20 years.
> Some code will rely on it. I know mine does.
>
> There are two strategies for dealing with default arguments that I know
> of: early binding and late binding. Python has early binding: the default
> argument is evaluated once, when the function is created. Late binding
> means the default argument is always re-evaluated each time it is needed.
>
> Both strategies are reasonable choices. Both have advantages and
> disadvantages. Both have use-cases, and both lead to confusion when the
> user expects one but gets the other. If you think changing from early to
> late binding will completely eliminate the default argument "gotcha", you
> haven't thought things through -- at best you might reduce the number of
> complaints, but only at the cost of shifting them from one set of use-
> cases to another.
>
> Early binding is simple to implement and simple to explain: when you
> define a function, the default value is evaluated once, and the result
> stored to be used whenever it is needed. The disadvantage is that it can
> lead to unexpected results for mutable arguments.
>
> Late binding is also simple to explain, but a little harder to implement.
> The function needs to store the default value as a piece of code (an
> expression) which can be re-evaluated as often as needed, not an object.
>
> The disadvantage of late binding is that since the expression is live, it
> needs to be calculated each time, even if it turns out to be the same
> result. But there's no guarantee that it will return the same result each
> time: consider a default value like x=time.time(), which will return a
> different value each time it is called; or one like x=a+b, which will
> vary if either a or b are changed. Or will fail altogether if either a or
> b are deleted. This will surprise some people some of the time and lead
> to demands that Python "fix" the "obviously buggy" default argument
> gotcha.
>
> If a language only offers one, I maintain it should offer early binding
> (the status quo). Why? Because it is more elegant to fake late binding in
> an early binding language than vice versa.
>
> To fake late binding in a language with early binding, use a sentinel
> value and put the default value inside the body of the function:
>
> * *def func(x, y=None):
> * * * *if y is None:
> * * * * * *y = []
> * * * *...
>
> All the important parts of the function are in one place, namely inside
> the function.
>
> To fake early binding when the language provides late binding, you still
> use a sentinel value, but the initialization code creating the default
> value is outside the body of the function, usually in a global variable:
>
> * *_DEFAULT_Y = [] *# Private constant, don't touch.
>
> * *def func(x, y=None):
> * * * *if y is None:
> * * * * * *y = _DEFAULT_Y
> * * * *...
>
> This separates parts of the code that should be together, and relies on a
> global, with all the disadvantages that implies.
>
>
>
> --
> Steven
> --
> http://mail.python.org/mailman/listinfo/python-list

 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
.NET 2.0 ASPx Page does not load, but HTM does prabhupr@hotmail.com ASP .Net 1 02-08-2006 12:57 PM
Button OnClick does not fire on first postback, but does on second Janet Collins ASP .Net 0 01-13-2006 10:08 PM
Does the 2.0 Framework come out when Visual Studio .NET 2005 does? needin4mation@gmail.com ASP .Net 3 10-07-2005 12:55 AM
CS0234 Global does not exist ... but it genuinely does Bill Johnson ASP .Net 0 07-08-2005 06:34 PM
Does no one else think microsoft does a poor job? =?Utf-8?B?SmVyZW15IEx1bmRncmVu?= Wireless Networking 2 11-20-2004 12:17 AM



Advertisments