Multi-dimensional list initialization

S

Steven D'Aprano

Yes. But this isn't going to cost any more time than figuring out
whether or not the list multiplication is going to cause quirks, itself.
Human psychology *tends* (it's a FAQ!) to automatically assume the
purpose of the list multiplication is to pre-allocate memory for the
equivalent (using lists) of a multi-dimensional array. Note the OP even
said "4d array".

I'm not entirely sure what your point is here. The OP screwed up -- he
didn't generate a 4-dimensional array. He generated a 2-dimensional
array. If his intuition about the number of dimensions is so poor, why
should his intuition about list multiplication be treated as sacrosanct?

As they say, the only truly intuitive interface is the nipple. There are
many places where people's intuition about programming fail. And many
places where Fred's intuition is the opposite of Barney's intuition.

Even more exciting, there are places where people's intuition is
*inconsistent*, where they expect a line of code to behave differently
depending on their intention, rather than on the code. And intuition is
often sub-optimal: e.g. isn't it intuitively obvious that "42" + 1 should
give 43? (Unless it is intuitively obvious that it should give 421.)

So while I prefer intuitively obvious behaviour where possible, it is not
the holy grail, and I am quite happy to give it up.

The OP's original construction was simple, elegant, easy to read and
very commonly done by newbies learning the language because it's
*intuitive*. His second try was still intuitive, but less easy to read,
and not as elegant.

Yes. And list multiplication is one of those areas where intuition is
suboptimal -- it produces a worse outcome overall, even if one minor use-
case gets a better outcome.

I'm not disputing that [[0]*n]*m is intuitively obvious and easy. I'm
disputing that this matters. Python would be worse off if list
multiplication behaved intuitively.

An analogy: the intuitively obvious thing to do with a screw is to bang
it in with a hammer. It's long, thin, has a point at the end, and a flat
head that just screams "hit me". But if you do the intuitive thing, your
carpentry will be *much worse* than the alternatives -- a hammered in
screw holds much less strongly than either a nail or a screwed in screw.
The surface area available for gripping is about 2% compared to a nail
and about 0.01% compared to a screw used correctly.

Having list multiplication copy has consequences beyond 2D arrays. Those
consequences make the intuitive behaviour you are requesting a negative
rather than a positive. If that means that newbie programmers have to
learn not to hammer screws in, so be it. It might be harder, slower, and
less elegant to drill a pilot hole and then screw the screw in, but the
overall result is better.

* Consistency of semantics is better than a plethora of special
cases. Python has a very simple and useful rule: objects should not
be copied unless explicitly requested to be copied. This is much
better than having to remember whether this operation or that
operation makes a copy. The answer is consistent:

Bull. Even in the last thread I noted the range() object produces
special cases.
range(0,5)[1] 1
range(0,5)[1:3]
range(1, 3)

What's the special case here? What do you think is copied?

You take a slice of a tuple, you get a new tuple.

You take a slice of a list, you get a new list.

You take a slice of a range object, you get a new range object.

I'm honestly not getting what you think is inconsistent about this.


The principle involved is that it gives you what you *usually* want;

Who is the "you" that decides what "you" usually want? And how do they
know what is "usual"?

Two-dimensional arrays in Python using lists are quite rare. Anyone who
is doing serious numeric work where they need 2D arrays is using numpy,
not lists. There are millions of people using Python, so it's hardly
surprising that once or twice a year some newbie trips over this. But
it's not something that people tend to trip over again and again and
again, like C's "assignment is an expression" misfeature.

I read some of the documentation on why Python 3 chose to implement it
this way.

What documentation is this? Because this is a design decision that goes
all the way back to at least Python 1.5:

[steve@ando ~]$ python1.5
Python 1.5.2 (#1, Aug 27 2012, 09:09:18) [GCC 4.1.2 20080704 (Red Hat
4.1.2-52)] on linux2
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
[[0, 99, 0, 0, 0], [0, 99, 0, 0, 0], [0, 99, 0, 0, 0]]


So I expect the design decision for Python 3 was "we made the right
decision before, there's no need to change it".


(pardon me for belabouring the point here)

Q: Does [0]*10 make ten copies of the integer object? A: No, list
multiplication doesn't make copies of elements.

Neither would my idea for the vast majority of things on your first
list.

Um, yes? The point is that "vast majority" is not "everything". Hence,
your suggested behaviour is inconsistent.


Q: What about [[]]*10?
A: No, the elements are never copied.

YES! For the obvious reason that such a construction is making mutable
lists that the user wants to populate later. If they *didn't* want to
populate them later, they ought to have used tuples -- which take less
overhead. Who even does this thing you are suggesting?!

Who knows? Who cares? Nobody does:

n -= n

instead of just n=0, but that doesn't mean that we should give it some
sort of special meaning different from n -= m. If it turns out that the
definition of list multiplication is such that NOBODY, EVER, uses [[]]*n,
that is *still* not a good reason for special-casing it. All it means is
that this will be a less-obscure example of the billions of things which
can be done in Python but nobody wants to.

You have quoted from the Zen of Python a few times in this post. Perhaps
you missed one of the most critical ones?

Special cases aren't special enough to break the rules.

There are perfectly good ways to generate a 2D array out of lists, and
even better reasons not to use lists for that in the first place. (Numpy
arrays are much better suited for serious work.)

They aren't list multiplication compatible in any event! It's a total
nonsense objection.

I'm afraid you've just lost an awful lot of credibility there.

py> x = [{}]*5
py> x
[{}, {}, {}, {}, {}]
py> x[0]['key'] = 1
py> x
[{'key': 1}, {'key': 1}, {'key': 1}, {'key': 1}, {'key': 1}]

And similarly for any other mutable object.

If you don't understand that lists can contain other mutable objects
apart from lists, then you really shouldn't be discussing this issue.

RARE!!!! You are NUTS!!!!

Yes, rare. I base that on about 15 years of Python coding and many
thousands (tens of thousands?) of hours on Python forums like this one.
What's your opinion based on?

List multiplication is rare enough, but when it is used, it is usually
used to generate a 1D array like this:

values = [None]*n # or 0 is another popular starting value

Using it twice to generate a 2D array is even rarer.

Give an example usage of why someone would want to do this. Then we can
discuss it.

Proxying objects is hardly a rare scenario. Delegation is less common
since you can subclass built-ins, but it is still used. It is a standard
design pattern.

Please link to the objection being proposed to the developers, and their
reasoning for rejecting it.
I think you are exaggerating.

Python is a twenty year old language. Do you really think this is the
first time somebody has noticed it?

It's hard to search for discussions on the dev list, because the obvious
search terms bring up many false positives. But here are a couple of bug
reports closed as "won't fix":

http://bugs.python.org/issue1408
http://bugs.python.org/issue12597

I suspect it is long past time for a PEP so this can be rejected once and
for all.

List multiplication [x]*n is conceptually equivalent to: <snip>
This is nice and simple and efficient.
No it isn't efficient. It's *slow* when done as in your example.

Well of course it is slow*er* when you move it from low-level C to high
level Python, but it is still fast.
I don't think so -- again, look at range(); it was made to work
inconsistent for a "common" case.

You mentioned range before, but it isn't clear to me what you think is
inconsistent about it.

Besides, 2D arrays are *not* rare and people *have* to copy internals of
them very often.

So you say.

The copy speed will be the same or *faster*, and the typing less -- and
the psychological mistakes *less*, the elegance more.

You think that it is *faster* to copy a list than to make a new pointer
to it? Your credibility is not looking too good here.

It's hardly going to confuse anyone to say that lists are copied with
list multiplication, but the elements are not.

Well, that confuses me. What about a list where the elements are lists?
Are they copied?

What about other mutable objects? Are they copied?

What about mutable objects which are uncopyable, like file objects?

Every time someone passes a list to a function, they *know* that the
list is passed by value -- and the elements are passed by reference.

And there goes the last of your credibility. *You* might "know" this, but
that doesn't make it so.

Python does not use either call-by-value or call-by-reference, and it
certainly doesn't use different calling conventions for different
arguments or parts of arguments. Everything is passed using the same
calling convention. Start here:

http://mail.python.org/pipermail/tutor/2010-December/080505.html

People in Python are USED to lists being "the" way to weird behavior
that other languages don't do.

Python's calling behaviour is identical to that used by languages
including Java (excluding unboxed primitives) and Ruby, to mention only
two.

You're starting to shout and yell, so perhaps it's best if I finish this
here.
 
R

rusi

I prefer the term "reference semantics".

Ha! That hits the nail on the head.

To go back to the OP:

So, here I was thinking "oh, this is a nice, easy way to initialize a 4D matrix" (running 2.7.3, non-core libs not allowed):

m = [[None] * 4] * 4

The way to get what I was after was:

m = [[None] * 4, [None] * 4, [None] * 4, [None * 4]]

(Obviously, I could have just hardcoded the initialization, but I'm too lazy to type all that out ;))

The behaviour I encountered seems a little contradictory to me. [None] * 4 creates four distinct elements in a single array while [[None] * 4] * 4 creates one distinct array of four distinct elements, with three references to it:
a = [None] * 4
a[0] = 'a'
a

['a', None, None, None]
m = [[None] * 4] * 4
m[0][0] = 'm'
m

[['m', None, None, None], ['m', None, None, None], ['m', None, None, None], ['m', None, None, None]]

Is this expected behaviour and if so, why? In my mind either result makessense, but the inconsistency is what throws me off.

m=[[None] * 2] * 3

is the same as

m=[[None]*2, [None]*2, [None]*2]

until one starts doing things like

m[0][0] = 'm'

So dont do it!

And to get python to help you by saying the same that I am saying do
m=((None) * 2) * 3
(well almost... its a bit more messy in practice)
m=(((None,) * 2),)*3

After that try assigning to m[0][0] and python will kindly say NO!

tl;dr version:
reference semantics is ok
assignment is ok (well up to a point)
assignment + reference semantics is not
 
S

Steven D'Aprano

I prefer the term "reference semantics".


Oh good, because what the world needs is yet another name for the same
behaviour.

- call by sharing
- call by object sharing
- call by object reference
- call by object
- call by value, where "values" are references
(according to the Java community)
- call by reference, where "references" refer to objects, not variables
(according to the Ruby community)
- reference semantics


Anything else?

http://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_sharing
 
R

Roy Smith

Steven D'Aprano said:
Oh good, because what the world needs is yet another name for the same
behaviour.

- call by sharing
- call by object sharing
- call by object reference
- call by object
- call by value, where "values" are references
(according to the Java community)
- call by reference, where "references" refer to objects, not variables
(according to the Ruby community)
- reference semantics


Anything else?

http://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_sharing

Call by social network? The called function likes the object.
Depending on how it feels, it can also comment on some of the object's
attributes.
 
G

Gregory Ewing

Roy said:
Call by social network? The called function likes the object.
Depending on how it feels, it can also comment on some of the object's
attributes.

And then finds that it has inadvertently shared all its
private data with other functions accessing the object.
 
G

Gregory Ewing

If anything is to be done in this area, it would be better
as an extension of list comprehensions, e.g.

[[None times 5] times 10]

which would be equivalent to

[[None for _i in xrange(5)] for _j in xrange(10)]
 
J

Jussi Piitulainen

Steven said:
Oh good, because what the world needs is yet another name for the
same behaviour.

- call by sharing
- call by object sharing
- call by object reference
- call by object
- call by value, where "values" are references
(according to the Java community)
- call by reference, where "references" refer to objects, not variables
(according to the Ruby community)
- reference semantics

Anything else?

http://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_sharing

Something else:

There's a call-by-* versus pass-by-* distinction, where the call-by-*
would be rather different from any of the above:

- call-by-value is what most languages now use: argument expressions
are reduced to values before they are passed to the function /
procedure / method / whatever.

- call-by-name was something Algol 60 had by default: something like
evaluating the argument expression every time its value is needed

- call-by-need: argument expression is reduced to a value the first
time its value is needed (if ever)

- call-by-lazy (increasingly silly terminology, and I don't quite have
an idea what it means in contrast to call-by-need)

The modern confusions would then be mostly over the pass-by-* family,
invariably using call-by-value in the above sense. The terminology for
these tends to produce more heat than light, but I think the relevant
distinctions are mostly just these:

- can one modify the argument effectively [Python: yes]

- can one modify the parameter with abandon [Python: don't]

- can one swap [Python: no]

- possibly: is it expensive to pass large objects? [Python: no]

The actual rule in Scheme, Java, and Python is the same simple and
sane rule: what are passed are values (argument expressions are fully
evaluated before the actual call takes place), parameter passing does
not involve any (observable) copying, and the arguments are bound to
fresh variables (no aliasing of variables).

Different communities use different words. Sometimes they use the same
words about different things. Resulting in more heat than light :(

(I'd have a few more things in the something-else department, but this
is already much longer than I thought. Ends.)
 
W

wxjmfauth

Le mercredi 7 novembre 2012 02:55:10 UTC+1, Steven D'Aprano a écrit :
Two-dimensional arrays in Python using lists are quite rare. Anyone who

is doing serious numeric work where they need 2D arrays is using numpy,

not lists. There are millions of people using Python, so it's hardly

surprising that once or twice a year some newbie trips over this. But

it's not something that people tend to trip over again and again and

again, like C's "assignment is an expression" misfeature.
Traceback (most recent call last):
File said:
from vmio6 import *
from svdecomp6 import *
mm = NewMat(3, 3)
mm [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]
mm[0][0] = 1.0; mm[0][1] = 2.0; mm[0][2] = 3.0
mm[1][0] = 11.0; mm[1][1] = 12.0; mm[1][2] = 13.0
mm[2][0] = 21.0; mm[2][1] = 22.0; mm[2][2] = 23.0
pr(mm, 'mm=')
mm=
( 1.00000e+000 2.00000e+000 3.00000e+000 )
( 1.10000e+001 1.20000e+001 1.30000e+001 )
( 2.10000e+001 2.20000e+001 2.30000e+001 )aa=
( -8.08925e-002 -9.09280e-001 4.08248e-001 )
( -4.77811e-001 -3.24083e-001 -8.16497e-001 )
( -8.74730e-001 2.61114e-001 4.08248e-001 )b=
( 4.35902e+001 1.37646e+000 1.93953e-016 )cc=
( -5.43841e-001 7.33192e-001 4.08248e-001 )
( -5.76726e-001 2.68499e-002 -8.16497e-001 )
( -6.09610e-001 -6.79492e-001 4.08248e-001 )aa * bb * cct=
( 1.00000e+000 2.00000e+000 3.00000e+000 )
( 1.10000e+001 1.20000e+001 1.30000e+001 )
( 2.10000e+001 2.20000e+001 2.30000e+001 )[[0.9999999999999991, 1.9999999999999993, 2.9999999999999982],
[10.999999999999995, 11.99999999999999, 12.999999999999996],
[20.999999999999986, 21.999999999999975, 22.999999999999986]]



jmf
 
E

Ethan Furman

Oscar said:
If anything is to be done in this area, it would be better
as an extension of list comprehensions, e.g.

[[None times 5] times 10]

which would be equivalent to

[[None for _i in xrange(5)] for _j in xrange(10)]

I think you're right that the meaning of list-int multiplication
can't/shouldn't be changed if this way.

A multidimensional list comprehension would be useful even for people
who are using numpy as it's common to use a list comprehension to
initialise a numpy array.

A more modest addition for the limited case described in this thread
could be to use exponentiation:
[[0, 0, 0], [0, 0, 0]]

What would happen with

--> [{}] ** (2, 3)

or

--> [my_custom_container()] ** (2, 3)

?

~Ethan~
 
E

Ethan Furman

After this post the only credibility you have left (with me, anyway) is that you seem to be willing
to learn. So learn the way Python works before you try to reimplement it.

~Ethan~
 
P

Prasad, Ramit

Gregory said:
And then finds that it has inadvertently shared all its
private data with other functions accessing the object.

And this is where Dihedral 8888 (or whatever the bot is called)
tells you that Python has no "private" variables. :)


~Ramit


This email is confidential and subject to important disclaimers and
conditions including on offers for the purchase or sale of
securities, accuracy and completeness of information, viruses,
confidentiality, legal privilege, and legal entity disclaimers,
available at http://www.jpmorgan.com/pages/disclosures/email.
 
M

MRAB

Oh good, because what the world needs is yet another name for the same
behaviour.

- call by sharing
- call by object sharing
- call by object reference
- call by object
- call by value, where "values" are references
(according to the Java community)
- call by reference, where "references" refer to objects, not variables
(according to the Ruby community)
- reference semantics


Anything else?

http://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_sharing
The disadvantage of calling it "call by ..." is that it suggests that
you're just talking about calling functions.

What about binding in general, eg "x = y"? Does it make sense to still
call it "call by ..."?
 
I

Ian Kelly

Interesting, you avoided the main point "lists are copied with list
multiplication".

It seems that each post is longer than the last. If we each responded
to every point made, this thread would fill a book.

Anyway, your point was to suggest that people would not be confused by
having list multiplication copy lists but not other objects, because
passing lists into functions as parameters works in basically the same
way. Except that it does not work the same way, because when lists
are passed into functions, they are not copied at all. Nor are are
any of their contents copied, lists or not. So actually I did address
this point with the "call-by-object" tangent; I just did not
explicitly link it back to your thesis.
But, in any event:
Pass by value (not call by value) is a term stretching back 30 years; eg:
when I learned the meaning of the words. Rewording it as "Call by value" is
something that happened later, and the nuance is lost on those without a
very wide programming knowledge *and* age.

Potayto, potahto. The distinction that you're describing is between
"strict" versus "non-strict" evaluation strategies. Hinging the
distinction on the non-descriptive words "call" and "pass" is lazy
terminology that should never have been introduced in the first place.
In any event:
All objects in Python are based on pointers; all parameters passed to
functions, etc, are *copies* of those pointers; (by pointer value).

No, all parameters passed to functions are *objects*. Python itself
has no concept of pointers. What you describe is true as an
implementation detail for CPython but not necessarily true for other
implementations, and not true at all for an abstract
(implementation-independent) view of the language.
I made the distinction between contents of the list and the list object
itself for that reason; I gave an explicit correction to the pass by "value"
generalization by saying: ("the elements are passed by reference").

The elements are not passed anywhere. Only the list object is passed
to the function, which is completely agnostic of the fact that the
list object happens to contain other objects.
 
M

Mark Lawrence

You're doing extremely well, you've overtaken Xah Lee as the biggest
waste of space on this list.
 
S

Steven D'Aprano

The disadvantage of calling it "call by ..." is that it suggests that
you're just talking about calling functions.

*shrug*

There are already two synonyms for this, "call by ..." and "pass by ...".
They are old, venerable terms dating back to Algol and possibly even
older. All the way back to Fortran perhaps?

What about binding in general, eg "x = y"? Does it make sense to still
call it "call by ..."?

Sure, why not? The person who prepares beef tartare or sushimi is still
called the cook.
 
O

Oscar Benjamin

On 7 November 2012 11:11, Oscar Benjamin <[email protected]>
wrote:
A more modest addition for the limited case described in this thread
could
be to use exponentiation:

[0] ** (2, 3)
[[0, 0, 0], [0, 0, 0]]

Exponentiation is expected to be asymmetric and is currently unused so
there is no ambiguity. The problem is if someone has already
subclassed list and added an exponentiation method.

How is that a problem? They just wont get the functionality.

This is absolutely contrived but:
Library A defines a subclass of list that adds an exponentiation
operator thinking that it's okay to still use these objects as lists.
Library B has an API that expects a list and tries to use the list
copy-exponentiation on its input. A user passes a list type object
from library A into library B and hopefully gets an error but possibly
gets a subtle bug that is hard to track down.

It doesn't sound plausible to me but at least in principle there is a
backward compatibility problem.
That said, losing:
[0] * (2, 3) == [0] * [2, 3]
would mean losing duck-typing in general. *Thus*, I fully agree with your
choice of exponentiation.

Also there's no reason why tuples couldn't have the same
exponentiation operator (although for them it would be no different
from repeated multiplication).


Oscar
 
I

Ian Kelly

Draw up some use cases for the multiplication operator (I'm calling on your
experience, let's not trust mine, right?); What are all the Typical ways
people *Do* to use it now?

If those use cases do not *primarily* center around *wanting* an effect
explicitly caused by reference duplication -- then it may be better to
abolish list multiplication all together; and rather, improve the list
comprehensions to overcome the memory, clarity, and speed pitfalls in the
most common case of initializing a list.

Why? Just to get rid of an FAQ?

Here's one of the more interesting uses from my own code:

values = zip(samples, times * num_groups)
if len(values) < len(times) * num_groups:
# raise an error

Converting that multiplication to a generator expression would look like this:

values = zip(samples, (t for _ in range(num_groups) for t in times))

That's not particularly hairy, but I do assert that it is
substantially less readable, and more so because it loses the symmetry
with the following if condition.

The recipes in the itertools docs also include this example, which
notably depends on the list containing multiple references to the same
iterator:

def grouper(n, iterable, fillvalue=None):
"Collect data into fixed-length chunks or blocks"
# grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx
args = [iter(iterable)] * n
return izip_longest(fillvalue=fillvalue, *args)

Replacing the list multiplication in that function with a list
comprehension would be awkward, as the obvious replacement of
[iter(iterable) for _ in range(n)] would produce different results.

For example, in initialization use cases; often the variable of a for loop
isn't needed and all the initializers have parameters which only need to be
evaluated *once* (no side effects).

Hence, there is an opportunity for speed and memory gains,while maintaining
clarity and *consistency*.

Some ideas of use cases:
[ (0) in xrange(10) ] # The function to create a tuple cache's the
parameter '0', makes 10 (0)'s
[ dict.__new__(dict) in xrange(10) ] # dict.__new__, The dict parameter is
cached -- makes 10 dicts.
[ lambda x:(0) in xrange(10) ] # lambda caches (0), returns a *reference* to
it multiple times.

How exactly do you propose to indicate to the compiler which parts of
the expressions are meant to be cached, and which are not?
Bull. Even in the last thread I noted the range() object produces
special cases.
range(0,5)[1]
1
range(0,5)[1:3]
range(1, 3)

What's the special case here? What do you think is copied?


You take a slice of a range object, you get a new range object.

You were'nt paying attention, OCCASIONALLY, get an integer, or a list.
2

LOOOOK! That's not a range object, that's an integer. Use Python 3.2 and
try it.

Of course you got an integer. You took an index of the range object,
not a slice. The rule is that taking an index of a sequence returns
an element; taking a slice of a sequence returns a sub-sequence. You
still have not shown any inconsistency here.
Game programmers routinely use 2D lists to represent the screen layout;
For example, they might use 'b' to represent a brick tile, and 'w' to
represent a water tile.

In many cases it may be simpler to use a plain list of strings:

screen = [
"sssss",
"ssbss",
"sbbbs",
"bbbbb",
]
py> x = [{}]*5
py> x
[{}, {}, {}, {}, {}]

No, I showed what happed when you do {}*3;
That *DOESN'T* work; You aren't multiplying the dictionary, you are
multiplying the LIST of dictionaries. Very different things.
You were complaining that my method doesn't multiply them -- well, gee --
either mine DOES or python DOESN'T. Double standards are *crap*.

No, he wasn't. He was talking about multiplying lists of dicts, and
whether the dicts are then copied or not, just like every other Q&A
item in that dialogue was concerning whether item X in a list should
expect to be copied when the containing list is multiplied.

You are the only one talking about applying the multiplication
operator to dicts.
Huh?
I'm not yelling any more than you are. Are ???YOU??? yelling?

Perhaps you're not aware that on the Internet, TYPING IN ALL CAPS is
commonly construed as SHOUTING.
 
S

Steven D'Aprano

Andrew, it appears that your posts are being eaten or rejected by my
ISP's news server, because they aren't showing up for me. Possibly a side-
effect of your dates being in the distant past? So if you have replied to
any of my posts, I haven't seen them.

In any case, I wanted to ask a question:


]
But, in any event:
Pass by value (not call by value) is a term stretching back 30 years;
eg: when I learned the meaning of the words. Rewording it as "Call by
value" is something that happened later, and the nuance is lost on
those without a very wide programming knowledge *and* age.

Every now and again I come across somebody who tries to distinguish
between "call by foo" and "pass by foo", but nobody has been able to
explain the difference (if any) to me. When you CALL a function, you PASS
values to it. Hence the two terms are effectively synonyms, and both
refer to the evaluation strategy when binding arguments to parameters.

If you believe that is incorrect, can you point me to something
explaining the difference?
 
A

Andrew Robinson

It seems that each post is longer than the last. If we each responded
to every point made, this thread would fill a book. It already is :)

Anyway, your point was to suggest that people would not be confused by
having list multiplication copy lists but not other objects, because
passing lists into functions as parameters works in basically the same
way.
Not quite; Although I wasn't clear; The variable passed in is by
*value* in contradistinction to the list which is by reference. Python
does NOT always default copy by reference *when it could*; that's the point.

Hence the programmer has to remember in foo( x,y ), the names x and y
when assigned to -- *DONT* affect the variables from which they came.
But any object internals do affect the objects everywhere.

A single exception exists; My thesis is for a single exception as well
-- I think Python allows that kind of thinking.
So actually I did address
this point with the "call-by-object" tangent; I just did not
explicitly link it back to your thesis.
My apology for not proof reading my statements for clarity. It was
definitely time for a nap back then.
Potayto, potahto. The distinction that you're describing is between
"strict" versus "non-strict" evaluation strategies. Hinging the
distinction on the non-descriptive words "call" and "pass" is lazy
terminology that should never have been introduced in the first place.
I would do it again. Other's have already begun to discuss terminology
with you -- I won't double team you.
 
O

Oscar Benjamin

Andrew, it appears that your posts are being eaten or rejected by my
ISP's news server, because they aren't showing up for me. Possibly a side-
effect of your dates being in the distant past? So if you have replied to
any of my posts, I haven't seen them.

In any case, I wanted to ask a question:


]
But, in any event:
Pass by value (not call by value) is a term stretching back 30 years;
eg: when I learned the meaning of the words. Rewording it as "Call by
value" is something that happened later, and the nuance is lost on
those without a very wide programming knowledge *and* age.

Every now and again I come across somebody who tries to distinguish
between "call by foo" and "pass by foo", but nobody has been able to
explain the difference (if any) to me. When you CALL a function, you PASS
values to it. Hence the two terms are effectively synonyms, and both
refer to the evaluation strategy when binding arguments to parameters.

If you believe that is incorrect, can you point me to something
explaining the difference?

Did you also miss MRAB's post above? It made sense to me.
 

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

No members online now.

Forum statistics

Threads
473,756
Messages
2,569,535
Members
45,008
Latest member
obedient dusk

Latest Threads

Top