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

S

Steven D'Aprano

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

Minus the managing it by hand part.

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 Python were C, then static variables would be the right solution, but
since it isn't, they aren't.
 
S

Steven D'Aprano

That's its main *advantage*.

Ah yes, sorry, poor wording on my part. Whether calculating the default
value *once* or *each time* is an advantage or disadvantage depends on
what you're trying to do. Either way, it could be just what you want, or
an annoying source of bugs.

It's hard to see anyone being confused by the resultant exception.

That's because you're coming at it from the perspective of somebody who
knows what to expect, in the middle of a discussion about the semantics
of late binding. Now imagine you're a newbie who has never thought about
the details of when the default value is created, but has a function like
"def foo(x, y=a+b)". He calls foo(x) seven times and it works, and on the
eighth time it blows up, perhaps with a NameError. It's surprising
behaviour, and newbies aren't good at diagnosing surprising bugs.

Or worse, it doesn't blow up at all, but gives some invalid value that
causes your calculations to be completely wrong. Exceptions are not the
worst bug to have -- they are the best.

It's
much harder to figure out what's going wrong with an early-bound
mutable.

Only for those who don't understand, or aren't thinking about, Python's
object model. The behaviour of early-bound mutables is obvious and clear
once you think about it, but it does require you to think about what's
going on under the hood, so to speak.

[...]
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:
[...]

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.

I do. All you've done is move the default from *before* the function is
defined to *after* the function is defined, instead of keeping it in the
function definition. It's still separate, and if the function is renamed
your code stops working. In other words, it violates encapsulation of the
function.

That's not to say that you shouldn't do this. It's a perfectly reasonable
technique, and I've used it myself, but it's not as elegant as the
current Python default argument behaviour.


[...]
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?

Excellent point.
 
S

Steven D'Aprano

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): ...

Point taken. Nevertheless, the semantics are still not the same as actual
early binding: if the global name is deleted or changed, the function
stops working.
 
L

Lie Ryan

which is to define the names "a", "b", and "c", and connects the three
names to the single object (integer 7 or new empty list).

note that this "connects" and "disconnecting" business is more commonly
referred to in python parlance as "binding" a name to an object.
 
A

alex23

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.

There is absolutely nothing stopping you from writing functions now
with that behaviour. All Python functions are "early binding, late
calling" with their arguments, if you treat the arguments as callables
within the function body.
 
A

alex23

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.

What on earth gave you the impression Python was bleeding edge? As
there's more to the language than its syntax, "breaking workflow"
disrupts the core library as much as it does the code of everyone
else.

More importantly, you're talking pap. Research is as much about
communication as programming; if you expect every single researcher in
a discipline (or even in the same _building_) to remain in perfect
lockstep with the version releases of a domain-relevant language,
you're either not a researcher or not a very good one. You should get
out to a conference occasionally and see what people think about your
"concept of the week" idea.
 
A

alex23

Only for those who don't understand, or aren't thinking about, Python's
object model. The behaviour of early-bound mutables is obvious and clear
once you think about it, but it does require you to think about what's
going on under the hood, so to speak.

And here we've come full circle to the point of this thread.

If Python was ever 'fixed' to prevent this issue, I'm pretty sure we'd
see an increase in the number of questions like the OP's.
 
T

Thomas Rachel

Am 22.12.2011 00:48 schrieb Steven D'Aprano:
For the amount of typing, it's easier to just do a straight line
tuple unpack
a,b,c = ([],[],[])

Note that tuples are created by the comma, not the round brackets (or
parentheses for any Americans reading). So the round brackets there are
strictly redundant:

a, b, c = [], [], []

The only times you need the brackets around a tuple is to control the
precedence of operations, or for an empty tuple.

IBTD:

a=((a, b) for a, b, c in some_iter)
b=[(1, c) for <whatever>]

Without the round brackets, it is a syntax error.


Thomas
 
T

Thomas Rachel

Am 22.12.2011 00:20 schrieb Dennis Lee Bieber:
The key one is that lists ([] defines a list, not an array) are
"mutable". Your "7" is not mutable.

Strictly spoken, that's only a "side show" where the effect is visible.

The real key concept is that [] creates *one* object which is then
assigned to the three names. That's the same for mutable and immutable
objects.

But only the modification which happens on mutable objects turns it into
a problem.

The rest o your explanation is 100% correct.


Thomas
 
T

Thomas Rachel

Am 21.12.2011 23:25 schrieb Eric:
Is it true that if I want to create an array or arbitrary size such
as:
for a in range(n):
x.append(<some function...>)

I must do this instead?
x=[]
for a in range(n):
x.append(<some function...>)

Of course - your x must exist before using it.
Now to my actual question. I need to do the above for multiple arrays
(all the same, arbitrary size). So I do this:
x=y=z=[]
for a in range(n):
x.append(<some function...>)
y.append(<some other function...>)
z.append(<yet another function...>)
Also, is there a more pythonic way to do "x=[], y=[], z=[]"?

You could do:

def create_xyz(n):
for a in range(n):
yield <some function...>, <some other function...>, \
<yet another function...>)

x, y, z = zip(*create_xyz(11))

or, if you want x, y, z to be lists,

x, y, z = [list(i) for i in zip(*create_xyz(11))]

..


Thomas
 
D

Devin Jeanpierre

If Python was ever 'fixed' to prevent this issue, I'm pretty sure we'd
see an increase in the number of questions like the OP's.

What makes you so sure? Both models do make sense and are equally
valid, it's just that only one of them is true. Is it just because
people already used to Python would get confused?

-- Devin
 
A

alex23

What makes you so sure? Both models do make sense and are equally
valid, it's just that only one of them is true. Is it just because
people already used to Python would get confused?

Because I believe that the source of confusion has far more to do with
mutable/immutable objects than with early/late binding. Masking or
'correcting' an aspect of Python's behaviour because novices make the
wrong assumption about it just pushes the problem elsewhere and
potentially makes the language inconsistent at the same time.
 
D

Devin Jeanpierre

Because I believe that the source of confusion has far more to do with
mutable/immutable objects than with early/late binding. Masking or
'correcting' an aspect of Python's behaviour because novices make the
wrong assumption about it just pushes the problem elsewhere and
potentially makes the language inconsistent at the same time.

That seems fairly silly -- foo.append(bar) obviously mutates
_something_ . Certainly it wasn't the source of my confusion when I
got caught on this. What makes you believe that the fundamental
confusion is about mutability?

(Also, if the change is applied everywhere, the language would not be
inconsistent.)

-- Devin
 
R

rusi

Thats hitting the nail on the head.
That seems fairly silly -- foo.append(bar) obviously mutates
_something_ . Certainly it wasn't the source of my confusion when I
got caught on this. What makes you believe that the fundamental
confusion is about mutability?

The confusion is not about mutability. Its about mutability and
parameter passing being non-orthogonal.
(Also, if the change is applied everywhere, the language would not be
inconsistent.)

Obviously that would depend on what the change is.
 
L

Lie Ryan

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.

I do. All you've done is move the default from *before* the function is
defined to *after* the function is defined, instead of keeping it in the
function definition. It's still separate, and if the function is renamed
your code stops working. In other words, it violates encapsulation of the
function.

Although we can solve that (default being after the function is defined)
using a simple decorator:

def funcargs(**args):
def __decorate_with_args(func):
for k,v in args.items():
setattr(func, k, v)
return func
return __decorate_with_args

Usage:

@funcargs(foo=4)
def bar(baz):
return baz + bar.foo

et voila, we had just reinvented early binding default argument, with a
much uglier syntax.
 
S

Steven D'Aprano

The only times you need the brackets around a tuple is to control the
precedence of operations, or for an empty tuple.

IBTD:

a=((a, b) for a, b, c in some_iter)
b=[(1, c) for <whatever>]

Without the round brackets, it is a syntax error.

Correction noted.

Nevertheless, the parentheses don't create the tuple, the comma operator
does.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,535
Members
45,007
Latest member
obedient dusk

Latest Threads

Top