why cannot assign to function call

140 of 147
Aaron Brady
On Jan 14, 3:51=A0am, Mark Wooding <m...@distorted.org.uk> wrote:
> Aaron Brady <castiro...@gmail.com> wrote:
> > On Jan 13, 5:06 pm, Mark Wooding <m...@distorted.org.uk> wrote:
> > snip
> > > I'm going to move away from the formal semantics stuff and try a
> > > different tack. =A0Here's what I think is the defining property of
> > > pass-by-value (distilled from the formal approach I described earlier,
> > > but shorn of the symbolism):
>
> > > =A0 The callee's parameters are /new variables/, initialized /as if by
> > > =A0 assignment/ from the values of caller's argument expressions.
>
> > In other words, the same as assignment in that language.
>
> It's the same as assignment of /values/ to /fresh variables/. =A0It's
> important to specify that argument expressions are fully evaluated, and
> that nothing gets overwritten in the process.
>
> So the difference between Python and C is that C copies values during
> assignment, but Python doesn't. =A0Therefore the argument passing works
> the same way.
That interferes with Joe's claim that the value of a variable is a
memory address. I think you'd have better luck sticking with "pass-by-
assignment".
A Java reference and a C++ reference are different things, somewhat
regrettably.
> > > Because its argument passing works the same way as its assignment.
>
> > That would be called pass-by-assignment. =A0But you've just postponed
> > explaining yourself. =A0Stop delegating and work.
>
> Huh? =A0I'm really at a loss to know what you want me to do.
You were remarkably civil about that. I regretted it as soon as I
sent it. You keep wanting to say "but it's just a reference, it's
just a reference". But all that does is put more burden on the fellow
that explains what a reference is. It took me years to grasp
pointers... possibly all the way up until comp. architecture class.
(As such, I find you're shirking.)
> I can't help it if people are confused over the phrase `pass-by-value',
> beyond trying to explain what it means using clear definitions. =A0The
> `value' part means that argument expressions are /evaluated/ (turned
> into values), which forces applicative-order semantics rather than (say)
> normal-order.
I can't believe it's taken this long, including October's discussion
(<name dropping> and especially with my dual (not duel, ha!) in
Philosophy </dropping>), but I'd finally like to quote the manuals.
"An object's identity never changes once it has been created."
"The value of some objects can change."
- http://docs.python.org/reference/datamodel.html
Therefore, an object's value is not its identity. (Its identity is
something other than its value.) You might get away with 'pass-by-
identity'.
After 'X=3D [1, 2, 3]', X refers to an object. You can change two
things: (1) the fact that X refers to that object, (2) that object.
In 'X=3D None' and 'del X', you change the fact that X refers to that
object. In 'X[:]=3D [4, 5, 6]' and 'X.pop()', you change the object.
Explanatory note: It really takes two sentences to say that. 'X
refers to an object. You can change the object, or change the fact
that X refers to it.' If you try to say it in one sentence, the
distinction collapses: 'You can change (1) the object that X refers
to, or (2) the object that X refers to.' Irony!
'X' is a key in a namespace.
namespace[ 'X' ]=3D [1, 2, 3]
namespace[ 'Y' ]=3D namespace[ 'X' ]
del namespace[ 'X' ]
No matter how hard you try, if you don't have both of two things,
'namespace' and '"X"', you can't change namespace[ 'X' ].
And back to the OP (long since gone, naturally): You can do:
f()[:]=3D [1, 2, 3]
As well as:
f().replace( [1, 2, 3] )
As well as:
f().contents=3D [1, 2, 3]
The particular thing s/he wanted isn't one of these.
Lastly, I think it would do you and Joe good to ignore some details,
when thinking about beginners.
--
http://mail.python.org/mailman/listinfo/python-list
-->

why cannot assign to function call

141 of 147
Lie
On Jan 5, 9:03=A0am, Derek Martin <c...@pizzashack.org> wrote:
> On Sat, Jan 03, 2009 at 10:15:51AM +0000, Marc 'BlackJack' Rintsch wrote:
> > On Fri, 02 Jan 2009 04:39:15 -0600, Derek Martin wrote:
>
> > > On Tue, Dec 30, 2008 at 02:21:29PM +0000, John O'Hagan wrote:
> > > What the Python community often overlooks, when this discussion again
> > > rears its ugly head (as it seems to every other hour or so), is that =
its
> > > assignment model is BIZARRE, as in it's conceptually different from
> > > virtually all other languages substantially taught in undergraduate
> > > computer science programs.
>
> > What's the difference between Python and Java or C# here!? =A0Or are th=
ey
> > also "BIZARRE"!?
>
> I am happily ignorant of C#. =A0As for Java, take the following code:
>
> =A0 a =3D 6;
> =A0 a =3D 5;
>
> In Python, when you execute the equivalent code, it causes two
> different objects to spring into existence, the first of which may be
> cleaned up by the GC (except that since we're using small integers,
> that's not likely to happen). =A0Unless I'm misinformed (which is very
> possible, my experience with Java has been extremely limited) in Java
> that's not the case... =A0the storage is allocated to the name a when
> you execute its declaration, and the *same storage* is reused upon
> subsequent assignment.
>
> That behaves exactly like named bins.
>
> > > And for that matter, it's pretty unintuitive generally.
>
> > Names and objects are quite "natural" IMHO. =A0There are many real world
> > objects which we attach one or more names to, or refer to in sequences
> > like "please give me the third book on that shelve" (``shelve[2]``).
>
> Indeed, but the way we assign names to them does not behave as it does
> in Python. =A0Nor does Python's assignment work like it does in algebra,
> or anywhere else the Python student is particularly likely to have
> seen variable assignment before encountering it in Python. =A0Let's
> define intuitive, shall we? =A0From dictionary.com (choosing the
> definition which most easily makes my point):
>
> =A0 intuitive: adj. =A0capable of being perceived or known by intuition.
>
> I'm going to go out on a limb and assert that there's NO POSSIBLE WAY
> a student could intuit Python's variable assignment behavior, having
> never been exposed to that same behavior prior. =A0It needs to be
> taught.
Which limb would you like to go with?
> > > That is, in what I'll call "normal" computer languages, a variable na=
me
> > > is thought of as the address of a bin where some data is stored, and =
the
> > > name is inexorably tied to that bin.
>
> > You just call that "normal" or "intuitive" because that's what you
> > learned first.
>
> In a sense, yes... but isn't that what intuition really is? =A0You can
> figure something out whithout being told how it works... =A0That's
> either because it's immediately obvious from observing it, or it
> behaves like something you've seen before. =A0That is what intitive is.
No. You've just said it yourself, something is intuitive if "it
behaves like something you've seen before". People have different
experience, people deeply involved in mathematics would see "named
bin" is the obvious thing to go, however commoners (including
mathematician) will have different say: "Derek is the name of a human
object" instead of "Derek is a specific collection of atoms that
resembles human".
This is also why python community tend to avoid the use of "variable",
because python's name doesn't behave like variables in mathematics, it
behaves like "name". (heck, one would argue that even "variable" in
programming is not exactly the same as "variable" in mathematic
either)
> > I think the "bin model" is more complex because you don't just have a
> > name and an object but always that indirection of the "bin".
>
> I cheerfully disagree. :) =A0"Named bins" is essentially how algebra
> works, and how several generations of computer languages, not to
> mention the actual machine language those generated, behaved, before
> the current crop. =A0Those interpretations came first, because, much as
> in the evolution of any other science, that was the model which was
> most intuitive or easily explained.
No, it is neither the most intuitive nor easily explained, but indeed
it is the simplest to implement as humans at that time have limited
capability to design chips. In the early days, ease of use is not a
priority, simplicity is.
> But you need not take my word for it. =A0Simply read the archives and
> see for yourself how much confusion this has caused on this list.
> [Please include the closely related behavior of parameter passing in
> your search.]
The first time I used python, I didn't have any idea how the object
model works, and I don't have experience in similar programming
concept nor was python my first language, yet I have no difficulty in
understanding why things goes like it is. For me, at least, python's
object model is intuitive (do you remember about the limb?).
--
http://mail.python.org/mailman/listinfo/python-list
-->

why cannot assign to function call

142 of 147
Rhodri James
On Fri, 27 Feb 2009 23:55:43 -0000, Ethan Furman <ethan@stoneleaf.us>
wrote:
I'm not Mark, but you did post to the whole group!
[snippety snip]
> Specifically, how is a new name (pbr) different, in Python, from a new
> name initialized as if by assignment (pbv)? It seems to me than you end
> up with the same thing in either case (in Python, at least), making the
> distinction non-existent.
>
> def func(bar):
> bar.pop()
>
> Pass-by-reference:
> foo = ['Ethan','Furman']
> func(foo) # bar = foo
>
> Pass-by-value:
> foo = ['Python','Rocks!']
> func(foo) # bar is new name for foo
With true pass-by-value, this comment is not true. Bar would be a copy
of foo, not a new name. What happens to bar is then not reflected in
foo (depending on how deep the copy is), which is the objective of
pass-by-value.
--
Rhodri James *-* Wildebeeste Herder to the Masses
--
http://mail.python.org/mailman/listinfo/python-list
-->

why cannot assign to function call

143 of 147
Mark Wooding
Ethan Furman <ethan@stoneleaf.us> writes:
> Mark Wooding wrote:
>> Here's what I think is the defining property of pass-by-value [...]:
>>
>> The callee's parameters are /new variables/, initialized /as if by
>> assignment/ from the values of caller's argument expressions.
>>
>> My soundbite definition for pass-by-reference is this:
>>
>> The callee's parameters are merely /new names/ for the caller's
>> argument variables -- as far as that makes sense.
>
> Greetings, Mark!
You posted to the whole group -- probably using a wide-reply option
while reading the mailing list. Still, I'll give an answer here in
order to help any other readers of the list or newsgroup. However, as
far as I'm concerned, this discussion is basically at an end and I'm not
really interested in starting it up all over again. To that end, I've
set followups to `poster'. I shall continue reply to private email
which seems interested in a sensible discussion.
> I was hoping you might be able to clarify those last two sound bites
> for me -- I think I understand what you are saying, but I'm confused
> about how they relate to Python...
>
> Specifically, how is a new name (pbr) different, in Python, from a new
> name initialized as if by assignment (pbv)? It seems to me than you
> end up with the same thing in either case (in Python, at least),
> making the distinction non-existent.
You've missed a level of indirection. In particular, `names' aren't
things that you initialize. They're things that you /bind/ to
variables. The crucial difference is that, in pass-by-value, new
variables are created as an intrinsic part of the process, whereas in
pass-by-reference, new variables are not (usually) created, and instead
the formal parameter names are bound to the caller's pre-existing
argument variables.
It's worth noting that I use the terms `name' and `binding' in different
ways from most of the Python community. This is unfortunate. The
discrepancy is actually because the Python meanings of these words are
not the same as the meanings in the wider computer science and
mathematical communities. For example, many Python users seem to use
`binding' to mean `assignment', which is a shame because it leaves the
concept that is usually called `binding' without a name. So I'll stick
with the wider meanings.
A while ago, I posted an essay -- to this group -- which may help
explain the concepts:
Message-ID: <8763k14nc6.fsf.mdw@metalzone.distorted.org.uk>
http://groups.google.com/group/comp.lang.python/msg/f6f1a321f819d02b
On with the question.
> def func(bar):
> bar.pop()
>
> Pass-by-reference:
> foo = ['Ethan','Furman']
> func(foo) # bar = foo
>
> Pass-by-value:
> foo = ['Python','Rocks!']
> func(foo) # bar is new name for foo
> # is this any different from above?
>
> If I have this right, in both cases foo will be reduced to a
> single-item list after func.
You're correct. So: we can conclude that the above test is not
sufficient to distinguish the two cases.
> Any further explanation you care to provide will be greatly
> appreciated!
This test is sufficient to distinguish:
def test(x):
x = 'clobbered'
y = 'virgin'
test(y)
print y
If it prints `virgin' then you have call-by-value. If it prints
`clobbered' then you have call-by-reference.
Let's examine the two cases, as I did in the essay I cited above. I'll
do call-by-value first. First, we define a function `test'. Then, we
initialize `y'. It's worth examining this process in detail. The name
`y' is initially unbound. so it is implicitly bound to a fresh
variable. Then, (a reference to) the string object 'virgin' is stored
in this variable. We can show this diagrammatically as follows.
y (in global env.) ====> [VAR] ---> 'virgin'
(In the diagrams, ===> denotes a binding relationship, between names and
variables; and ---> denotes a reference relationship, between variables
and values.)
Next, we call the `test' function. Call-by-value says that we must
evaluate the argument expressions. There's only one: `x'. The value of
a name is obtained by (a) finding which variable is bound to the name,
and (b) extracting the value from this variable. Well, the variable is
the one we just bound, and the value stored is (the reference to) the
string 'virgin'. So the result of evaluating the argument expressions
is simply (the reference to) that string.
The function has one parameter, `y'. A new environment is constructed
by extending the global environment. In this new environment, the name
`y' is bound to a fresh variable -- distinct from all others, and
especially from the variable bound to `x' -- and in that variable we
store the value of the corresponding argument expression. Result: the
function body is executed in an environment which is like the global
environment except that `y' is bound to a fresh variable containing
'virgin'.
y (in global env.) ====> [VAR] ---> 'virgin'
^
|
x (in function `test') ====> [VAR] -------'
Now there's an assignment
x = 'clobbered'
The name `x' is already bound to a variable. So we modify that variable
so that it stores (a reference to) the string 'clobbered'.
y (in global env.) ====> [VAR] ---> 'virgin'
x (in function `test') ====> [VAR] ---> 'clobbered'
And then the function ends. The environment we constructed is
forgotten. The variable bound to `x' is lost forever, since it wasn't
bound to any other name. Since modifying that variable was the only
action carried out in the function, and the variable is now lost, there
is no externally observable effect. When we finally print `y', we see
`virgin', because the variable bound to `y' was unchanged.
y (in global env.) ====> [VAR] ---> 'virgin'
So much for call-by-value. How about call-by-reference? Well,
everything is the same until the actual call. But then everything
changes.
Firstly, call-by-reference /doesn't/ evaluate the argument expressions.
Instead, we just note that `y' is bound to a particular variable in the
global environment. The function has a single parameter `x'. A new
environment is constructed by extending the global environment (again):
in this new environment, the name `x' is bound to /the same variable/
that `y' is bound to in the global environment.
y (in global env.) ====> [VAR] <===== x (in function `test')
|
|
v
'virgin'
Now we assign to `x'. The detailed rules are the same: `x' is bound, so
we modify the variable it's bound to, so that it stores 'clobbered'.
But this time, the variable being clobbered is the /same/ variable that
`y' is bound to. So finally, when we print `y', we see the string
'clobbered.
y (in global env.) ====> [VAR] <===== x (in function `test')
|
|
v
'clobbered'
Now, let's look at your example.
> def func(bar):
> bar.pop()
>
> foo = ['Ethan','Furman']
> func(foo) # bar = foo
I won't describe this in the same excruciating detail as I did for the
one above; but I will make some observations.
In call-by-reference, the environment in which the body of `func' is
executed is constructed by extending the global environment with a
binding of the name `bar' to the same variable as is bound to `foo' in
the global environment. There is only the one variable, so obviously
both names (considered as expressions) must evaluate to the same value.
I won't go into the details of method invocation, which in Python is
quite complicated; but `bar.pop()' mutates this value: it modifies it in
place. So, when the function returns, `foo' can be seen to print
differently. The value is, in some sense, the /same/ value as it was
before, in that it occupies the same storage locations, but the contents
of those storage locations has been altered.
foo =====> [VAR] <===== bar
(in global env) | (in function `func')
|
v
['Ethan', 'Furman']
In call-by-value, `func' executes in an environment constructed by
extending the global environment with a binding of `bar' to a /fresh/
variable. This fresh variable is then initialized: we store the value
of the argument expression `foo' into it. This value is a reference to
the list ['Ethan', 'Furman']. (See, I've stopped parenthesizing the
reference stuff, because here it really matters.) So, we have two
variables, bound to `foo' and `bar', but both variables refer to the
same list object. The body of `func' doesn't modify any variables;
rather, it mutates the list object in place (as above). Since both
variables refer to the /same/ list object, this mutation is still
observable outside of the function.
foo =====> [VAR] ---> ['Ethan', 'Furman']
(in global env) ^
|
bar =====> [VAR'] ------------'
(in function `func')
So the reason that your example doesn't distinguish the two cases is
because, in both cases, there's still only one value, which is mutated.
But there is a conceptual difference, unobservable in this instance,
because call-by-reference has two names bound to the same variable,
while call-by-value has two names, bound to /distinct/ variables but
which both refer to the same value.
Finally, it may be instructive to remove the issue of function calling
altogether. Consider:
foo = ['Ethan', 'Furman']
bar = foo
bar.pop()
What is the final value of `foo'?
Now:
x = 'virgin'
y = x
y = 'clobbered'
What is the final value of `x'?
Are you enlightened?
-- [mdw]
--
http://mail.python.org/mailman/listinfo/python-list
-->

why cannot assign to function call

144 of 147
Mark Wooding <mdw@distorted.org.uk> wrote:
> Ethan Furman <ethan@stoneleaf.us> writes:
>
> > Mark Wooding wrote:
> >> Here's what I think is the defining property of pass-by-value [...]:
> >>
> >> The callee's parameters are /new variables/, initialized /as if by
> >> assignment/ from the values of caller's argument expressions.
> >>
> >> My soundbite definition for pass-by-reference is this:
> >>
> >> The callee's parameters are merely /new names/ for the caller's
> >> argument variables -- as far as that makes sense.
> >
> > Greetings, Mark!
>
> You posted to the whole group -- probably using a wide-reply option
> while reading the mailing list. Still, I'll give an answer here in
> order to help any other readers of the list or newsgroup. However, as
> far as I'm concerned, this discussion is basically at an end and I'm not
> really interested in starting it up all over again. To that end, I've
> set followups to `poster'. I shall continue reply to private email
> which seems interested in a sensible discussion.
I just popped in to this thread, and read the whole thing in a marathon
session (why, I'm not quite sure, but somehow I found it interesting :).
I'm going to follow up here at the risk of annoying Mark, because I
think there may be a way to reconcile the language in a way that is
helpful in explaining things to Python beginners.
> > I was hoping you might be able to clarify those last two sound bites
> > for me -- I think I understand what you are saying, but I'm confused
> > about how they relate to Python...
I think this is the key point. I am an experienced Python programmer,
and I had to think long and hard about what you were saying, Mark, in
order to understand how it applied to Python. I think this means that
your model and/or your level of abstraction is not "natural" when trying
to understand Python programs, and I think I may have figured out why.
> > Specifically, how is a new name (pbr) different, in Python, from a new
> > name initialized as if by assignment (pbv)? It seems to me than you
> > end up with the same thing in either case (in Python, at least),
> > making the distinction non-existent.
>
> You've missed a level of indirection. In particular, `names' aren't
> things that you initialize. They're things that you /bind/ to
> variables. The crucial difference is that, in pass-by-value, new
> variables are created as an intrinsic part of the process, whereas in
> pass-by-reference, new variables are not (usually) created, and instead
> the formal parameter names are bound to the caller's pre-existing
> argument variables.
I think that the reason Ethan missed that level of indirection is that
Python hides that level of indirection from the programmer. And I think
that this is where the conversation between you and Steven got hung
up, Mark. You are firmly grounded in how languages work in general,
so your conceptual model naturally includes details that Steven's
conceptual model can ignore, because he's dealing only with the model
used by Python. He can (and wants to!) ignore the level of indirection
that Python doesn't even give him access to.
In short, there is a reason why you almost never hear the word 'variable'
from an experienced Python programmer when talking about the Python model.
That level detail is abstracted into something else by the Python
conceptual model: it becomes a namespace mapping names to objects.
> It's worth noting that I use the terms `name' and `binding' in different
> ways from most of the Python community. This is unfortunate. The
> discrepancy is actually because the Python meanings of these words are
> not the same as the meanings in the wider computer science and
> mathematical communities. For example, many Python users seem to use
> `binding' to mean `assignment', which is a shame because it leaves the
> concept that is usually called `binding' without a name. So I'll stick
> with the wider meanings.
It seems to me from reading the thread that everyone is actually in
pretty good agreement that 'name' is the symbol one types in the
program source to denote...something. It's the something where things
get tricky. So let's leave name itself alone.
But let's replace 'assignment' and 'binding' with the concept that is
more natural to Python's model, that of a "mapping". A namespace maps
names to objects. If I'm understanding you correctly, Mark, in your
model what I'm calling a namespace is the set of variable bindings in a
particular environment. Because Python does not allow the programmer
to actually rebind a variable (Python handles the association between
name and variable completely behind the scenes), Python programmers
naturally skip past that name-to-variable binding step, and conceptually
think about the name being bound to the _value_ instead. After all,
that's the only thing they can change. This may be unfortunate from
a precisionist viewpoint, but it is both natural and, IMO, inevitable,
because it makes thinking about Python programs more efficient.
> A while ago, I posted an essay -- to this group -- which may help
> explain the concepts:
>
> Message-ID: <8763k14nc6.fsf.mdw@metalzone.distorted.org.uk>
> http://groups.google.com/group/comp.lang.python/msg/f6f1a321f819d02b
>
> On with the question.
>
> > def func(bar):
> > bar.pop()
> >
> > Pass-by-reference:
> > foo = ['Ethan','Furman']
> > func(foo) # bar = foo
> >
> > Pass-by-value:
> > foo = ['Python','Rocks!']
> > func(foo) # bar is new name for foo
> > # is this any different from above?
> >
> > If I have this right, in both cases foo will be reduced to a
> > single-item list after func.
>
> You're correct. So: we can conclude that the above test is not
> sufficient to distinguish the two cases.
Suppose we recast this by saying that the function has a namespace,
and in that namespace 'bar' maps to something. What that something is
depends on what is passed in that argument position when func is called.
The calling code's namespace maps 'foo' to something. When func is
called, a mapping to that same object is set up in func's namespace,
under the name 'bar'.
In Python's conceptual model it doesn't make any sense to ask if
the argument is passed by value or by reference. In reality, if I'm
understanding you correctly Mark, you are absolutely correct that the
'variable' (the thing actually pointed to by 'name', which is a storage
location that contains a pointer to an object) is passed by value.
That is, the contents of the storage location that the caller's 'foo'
is pointing to is copied into a new storage location that the callee's
'bar' points to. Conceptually, however, that is an implementation detail.
Conceptually, the important thing is that a _new mapping_ in a _different
namespace_ is set up to the _same object_. That is the semantics that
Python the language defines.
As Steven said, talking about variables seems to be drilling down into
a level of abstraction that is more confusing than it is helpful when
you are actually _programming_ in Python. It can be a useful level of
abstraction to talk about when trying to explain what is happening to
someone with previous experience of other languages, but it isn't useful
(in my experience) for everyday Python programming.
The reason that is true is because in Python there is _no way_ to get
a second reference to the 'variable' that is pointed to by 'foo' or by
'bar'. (Unless you walk into that secret back room, of course, and even
then it is hard, I think.) As I said, Python hides that level of detail
from the programmer. This means that it effectively does not exist when
you program Python, and _this_ is why we get all tangled up in language
when we try to discuss it. You, Mark, had to go to great lengths to
define your terms, and still a lot of people didn't get it, because in
actual, on-the-ground Python programming, variables just don't enter in to
it, because they can not be directly manipulated from the Python program.
I suspect that the longer one has been a Python programmer, the harder
it is to even see this blind spot, and thus the harder it is to respond
sensibly to someone new to Python whose conceptual model includes
variables that _can_ be accessed and manipulated from the program. I know
_I_ had to read and re-read your posts before I finally figured it out
(assuming I have!). (FYI, my programming learning path was roughly
Basic->FORTRAN ->LISP->assembler->C->Python, and I've been programming
Python since 1993 or thereabouts).
(BTW, I am not claiming Python is unique in this regard, I'm just talking
about how it looks from inside the Python programming mindset.)
> > Any further explanation you care to provide will be greatly
> > appreciated!
>
> This test is sufficient to distinguish:
>
> def test(x):
> x = 'clobbered'
>
> y = 'virgin'
> test(y)
> print y
>
> If it prints `virgin' then you have call-by-value. If it prints
> `clobbered' then you have call-by-reference.
Or if you think in terms of namespace mappings, 'test' cannot change what
'y' is mapped to in the caller's namespace, because (in this function)
it has no mapping to that namespace object. It _can_ make changes to
the thing that y is mapped _to_, if that thing is mutable. That's the
most useful way to think about it that I've found. Your reasoning about
variables and call-by-reference and call-by-value, while correct and
enlightening at the computer science level, confuses me and makes me
less efficient if I think about it while writing a Python program.
> Let's examine the two cases, as I did in the essay I cited above. I'll
> do call-by-value first. First, we define a function `test'. Then, we
> initialize `y'. It's worth examining this process in detail. The name
> `y' is initially unbound. so it is implicitly bound to a fresh
> variable. Then, (a reference to) the string object 'virgin' is stored
> in this variable. We can show this diagrammatically as follows.
>
> y (in global env.) ====> [VAR] ---> 'virgin'
>
> (In the diagrams, ===> denotes a binding relationship, between names and
> variables; and ---> denotes a reference relationship, between variables
> and values.)
This diagram is IMO accurate and correct. However, in Python we can
never actually touch [VAR] except insofar as we change the right hand
side element of the diagram. And we can only do _that_ if we have access
to the object that is the global name space, and we mutate that object.
So for reasoning about Python programs, I think like this:
y (in global namespace) ---> 'virgin'
> Next, we call the `test' function. Call-by-value says that we must
> evaluate the argument expressions. There's only one: `x'. The value of
> a name is obtained by (a) finding which variable is bound to the name,
> and (b) extracting the value from this variable. Well, the variable is
> the one we just bound, and the value stored is (the reference to) the
> string 'virgin'. So the result of evaluating the argument expressions
> is simply (the reference to) that string.
>
> The function has one parameter, `y'. A new environment is constructed
> by extending the global environment. In this new environment, the name
> `y' is bound to a fresh variable -- distinct from all others, and
> especially from the variable bound to `x' -- and in that variable we
> store the value of the corresponding argument expression. Result: the
> function body is executed in an environment which is like the global
> environment except that `y' is bound to a fresh variable containing
> 'virgin'.
>
> y (in global env.) ====> [VAR] ---> 'virgin'
> ^
> |
> x (in function `test') ====> [VAR] -------'
I would describe this (in Python) as follows: we call the test function,
passing it x. This means we find the object that x is mapped to in
the caller's namespace, and we map 'y' in the function's namespace
to the same object:
y (in global env.) ---> 'virgin'
^
|
x (in function 'test') ---'
> Now there's an assignment
>
> x = 'clobbered'
>
> The name `x' is already bound to a variable. So we modify that variable
> so that it stores (a reference to) the string 'clobbered'.
>
> y (in global env.) ====> [VAR] ---> 'virgin'
>
> x (in function `test') ====> [VAR] ---> 'clobbered'
Assignment in Python changes a mapping (mutates a namespace object). In
this case the mapping being changed is the local function's namespace
mapping. We can't affect the caller's namespace mapping, because (in
this function) we don't have access to the caller's namespace object.
So I would diagram it like this:
y (in global env.) -----> 'virgin'
x (in function 'test') ----> 'clobbered'
> And then the function ends. The environment we constructed is
> forgotten. The variable bound to `x' is lost forever, since it wasn't
> bound to any other name. Since modifying that variable was the only
> action carried out in the function, and the variable is now lost, there
> is no externally observable effect. When we finally print `y', we see
> `virgin', because the variable bound to `y' was unchanged.
>
> y (in global env.) ====> [VAR] ---> 'virgin'
The function's local namespace is not accessible to the caller, so the
caller can't access the object 'clobbered'. Its own namespace mapping
is unchanged. The object 'virgin' is unchanged. So the caller sees no
change as a result of calling the function.
Now, to demonstrate that this model of namespaces-as-mappings (and
first level objects themselves) is more useful to the Python programmer
than the accurate but too-low-level description in terms of variables,
consider this:
>>> def test(x):
... x = 'clobbered'
... return locals()
>>> y = 'virgin'
>>> ns = test(y)
>>> print y
virgin
>>> print ns['x']
clobbered
In other words, while thinking about 'variables' and 'pass by value' is
_accurate_, it does not add anything to the conceptual model a programmer
needs in order to understand and write Python code. Namespaces as first
level objects that map names to values, however, is an extremely useful
and powerful mental model for programming in Python. The two concepts are
conceptually equivalent regardless of implementation details (assuming we
say a namespace immutably associates names with variables, which in turn
mutably point to values), but the namespace, IMO, is the more useful of
the two when reasoning about Python programs.
> So much for call-by-value. How about call-by-reference? Well,
> everything is the same until the actual call. But then everything
> changes.
>
> Firstly, call-by-reference /doesn't/ evaluate the argument expressions.
> Instead, we just note that `y' is bound to a particular variable in the
> global environment. The function has a single parameter `x'. A new
> environment is constructed by extending the global environment (again):
> in this new environment, the name `x' is bound to /the same variable/
> that `y' is bound to in the global environment.
>
> y (in global env.) ====> [VAR] <===== x (in function `test')
> |
> |
> v
> 'virgin'
And this is something that you can _never_ do in Python (short of
mindbogglingly bad hackery). I know you know that, but the fact that
you _cannot_ do it is why people start getting confused when talking
about call-by-reference vs call-by-value in a Python context.
> Now we assign to `x'. The detailed rules are the same: `x' is bound, so
> we modify the variable it's bound to, so that it stores 'clobbered'.
> But this time, the variable being clobbered is the /same/ variable that
> `y' is bound to. So finally, when we print `y', we see the string
> 'clobbered.
>
>
> y (in global env.) ====> [VAR] <===== x (in function `test')
> |
> |
> v
> 'clobbered'
>
> Now, let's look at your example.
>
> > def func(bar):
> > bar.pop()
> >
> > foo = ['Ethan','Furman']
> > func(foo) # bar = foo
>
> I won't describe this in the same excruciating detail as I did for the
> one above; but I will make some observations.
>
> In call-by-reference, the environment in which the body of `func' is
> executed is constructed by extending the global environment with a
> binding of the name `bar' to the same variable as is bound to `foo' in
> the global environment. There is only the one variable, so obviously
> both names (considered as expressions) must evaluate to the same value.
> I won't go into the details of method invocation, which in Python is
> quite complicated; but `bar.pop()' mutates this value: it modifies it in
> place. So, when the function returns, `foo' can be seen to print
> differently. The value is, in some sense, the /same/ value as it was
> before, in that it occupies the same storage locations, but the contents
> of those storage locations has been altered.
>
> foo =====> [VAR] <===== bar
> (in global env) | (in function `func')
> |
> v
> ['Ethan', 'Furman']
And just to be crystal clear, the above case is _not_ the one Python does.
> In call-by-value, `func' executes in an environment constructed by
> extending the global environment with a binding of `bar' to a /fresh/
> variable. This fresh variable is then initialized: we store the value
> of the argument expression `foo' into it. This value is a reference to
> the list ['Ethan', 'Furman']. (See, I've stopped parenthesizing the
> reference stuff, because here it really matters.) So, we have two
> variables, bound to `foo' and `bar', but both variables refer to the
> same list object. The body of `func' doesn't modify any variables;
> rather, it mutates the list object in place (as above). Since both
> variables refer to the /same/ list object, this mutation is still
> observable outside of the function.
>
> foo =====> [VAR] ---> ['Ethan', 'Furman']
> (in global env) ^
> |
> bar =====> [VAR'] ------------'
> (in function `func')
And for reasoning about Python programs, I would write this diagram
(which _is_ the way Python does it) as:
foo ---> ['Ethan', 'Furman']
(in global namespace) ^
|
bar ------------'
(in 'func' namespace)
> So the reason that your example doesn't distinguish the two cases is
> because, in both cases, there's still only one value, which is mutated.
> But there is a conceptual difference, unobservable in this instance,
Unobservable in Python because Python gives the programmer no way to
create two pointers to the same '[VAR]'.
> because call-by-reference has two names bound to the same variable,
> while call-by-value has two names, bound to /distinct/ variables but
> which both refer to the same value.
>
> Finally, it may be instructive to remove the issue of function calling
> altogether. Consider:
>
> foo = ['Ethan', 'Furman']
> bar = foo
> bar.pop()
>
> What is the final value of `foo'?
>
> Now:
>
> x = 'virgin'
> y = x
> y = 'clobbered'
>
> What is the final value of `x'?
>
> Are you enlightened?
Hopefully so! Your explanation of the underlying mechanics of call by
reference and call by value is the best I've ever seen, and all that
effort you put in to defining your terms earlier in this thread has
ultimately paid off.
Although this has been long enough already (especially since I quoted
your excellent article in full), I still want to go on a bit more.
I want to demonstrate the explanatory power of the namespace model,
which was one of your arguments with Steven: that his model of Python
working directly with values couldn't explain certain things.
On of the examples you gave (and I'm doing this from memory) was this:
>>> l = [ lambda: x for x in [1,2,3] ]
>>> [ f() for f in l ]
[3, 3, 3]
Your explanation was in terms of variables and environments, and frankly I
had a hard time following it (though I get it now). Here's how I explain
it in terms of namespaces: lambda creates a closure, and part of the
closure is a reference to the locals namespace object that exists when the
closure is formed. The (top level of the) list comprehension is operating
in the local namespace, so it is this namespace that gets captured inside
the closure. When we execute the functions later, what happens is that
'x' is looked up in this namespace, and the object that _it maps to at
that time_ is returned. Since the last assignment (mapping) to X was
to the object '3', '3' is what is returned by all three functions calls.
The code above is equivalent to the following code, which makes is
clearer what is happening:
>>> l2 = []
>>> for x in 1,2,3:
... l2.append(locals())
>>> [ ns['x'] for ns in l2 ]
[3, 3, 3]
So in terms of namespaces it is crystal clear why we get three 3s,
and why the following produces three 5s:
>>> x = 5
>>> [ ns['x'] for ns in l2 ]
[5, 5, 5]
>>> [ f() for f in l ]
[5, 5, 5]
This is conceptually equivalent to your variables and environments model,
but I think it is easier to understand and reason about, when talking
only about Python code, because it elides that level of indirection to
which the Python programmer has no access. At least, I find it so!
To take the final step toward unifying your view with Steven's view,
we have to admit that Python objects that reference other objects can be
viewed either as having [VAR] elements containing pointers to objects,
or as namespaces referencing other objects, and that these two views
are conceptually equivalent.
Thus, take your circular list example:
>>> a [1, 2, 3]
>>> a[1] = a
>>> a
[1, [...], 3]
Your view was that this list must contain a pointer variable which points
to the list itself (a reference), while Steven held that Python lists
reference other objects directly, and had no problem with this Tardis-like
construct. You are obviously correct that what a list is is a sequence of
variable slots that can point to any object, including the list itself.
But we can also view the list as a namespace whose name elements are a
zero-origined, monotonically increasing set of integers, and that within
this name space these names map directly to python objects.
In Python (or at least in CPython), the two views are equivalent. But I
argue, with Steven, that the latter view is more _useful_ for a Python
programmer, and that it is in fact Python's conceptual model.
I would also argue that the latter view is the one that it is more
helpful to explain to newcomers to Python, with due acknowledgement
being made to the reality of the behind-the-scenes variables for those
coming from a background where that will be helpful. The trick is to
get the long-time Python programmers to remember, when talking to such
a newcomer, that those variables even exist!
--RDM
--
http://mail.python.org/mailman/listinfo/python-list
-->

why cannot assign to function call

145 of 147
Tim Roberts
Ethan Furman <ethan@stoneleaf.us> wrote:
>
>Specifically, how is a new name (pbr) different, in Python, from a new
>name initialized as if by assignment (pbv)? It seems to me than you end
>up with the same thing in either case (in Python, at least), making the
>distinction non-existent.
>
>def func(bar):
> bar.pop()
>
>Pass-by-reference:
> foo = ['Ethan','Furman']
> func(foo) # bar = foo
The problem here, in my view, is the terminology. It is true that, inside
the function "func", "bar" will refer to the same list that "foo" refers
to. However, it is inaccurate to think of this list as "foo". There
exists a list -- an anonymous list in object space -- and while "func" is
executing, there are two names bound to that list. So, the "bar.pop()"
instruction changes that anonymous list, you'll see those changes if you
inspect "foo" after the call.
>Pass-by-value:
> foo = ['Python','Rocks!']
> func(foo) # bar is new name for foo
> # is this any different from above?
Yes, it's different. If Python really had "pass by value", that function
call would pass a COPY of the list. The function would receive the list's
"value", not the list itself.
>If I have this right, in both cases foo will be reduced to a single-item
>list after func. Any further explanation you care to provide will be
>greatly appreciated!
Nope. Under pass-by-value semantics, "func" could dance on the list to its
heart's content, and "foo" would not be changed.
--
Tim Roberts, timr@probo.com
Providenza & Boekelheide, Inc.
--
http://mail.python.org/mailman/listinfo/python-list
-->

why cannot assign to function call

146 of 147
Aahz
In article <mailman.952.1235850376.11746.python-list@python.org>,
<rdmurray@bitdance.com> wrote:
>
>I think this is the key point. I am an experienced Python programmer,
>and I had to think long and hard about what you were saying, Mark, in
>order to understand how it applied to Python. I think this means that
>your model and/or your level of abstraction is not "natural" when trying
>to understand Python programs, and I think I may have figured out why.
Good job! I'll just add one little bit to your excellent rebuttal:
>Assignment in Python changes a mapping (mutates a namespace object). In
>this case the mapping being changed is the local function's namespace
>mapping. We can't affect the caller's namespace mapping, because (in
>this function) we don't have access to the caller's namespace object.
>So I would diagram it like this:
>
> y (in global env.) -----> 'virgin'
>
> x (in function 'test') ----> 'clobbered'
Although you later make clear that this model works well with lists, too,
I personally find it easier to separate the concept of "name" (which is
strictly the set of legal Python identifiers) with the more general
concept of "target". Consider this:
>>> L = [1, 2, 3]
>>> L[1:1] = ['foo', 'bar']
What do you call ``L[1:1]``? I call that a target (and in fact, so do
the Python docs).
--
Aahz (aahz@pythoncraft.com) <*> http://www.pythoncraft.com/
"All problems in computer science can be solved by another level of
indirection." --Butler Lampson
--
http://mail.python.org/mailman/listinfo/python-list
-->

why cannot assign to function call

147 of 147
Terry Reedy
Aahz wrote:
> In article <mailman.952.1235850376.11746.python-list@python.org>,
> <rdmurray@bitdance.com> wrote:
>> I think this is the key point. I am an experienced Python programmer,
>> and I had to think long and hard about what you were saying, Mark, in
>> order to understand how it applied to Python. I think this means that
>> your model and/or your level of abstraction is not "natural" when trying
>> to understand Python programs, and I think I may have figured out why.
>
> Good job! I'll just add one little bit to your excellent rebuttal:
>
>> Assignment in Python changes a mapping (mutates a namespace object). In
>> this case the mapping being changed is the local function's namespace
>> mapping. We can't affect the caller's namespace mapping, because (in
>> this function) we don't have access to the caller's namespace object.
>> So I would diagram it like this:
>>
>> y (in global env.) -----> 'virgin'
>>
>> x (in function 'test') ----> 'clobbered'
>
> Although you later make clear that this model works well with lists, too,
> I personally find it easier to separate the concept of "name" (which is
> strictly the set of legal Python identifiers) with the more general
> concept of "target". Consider this:
>
>>>> L = [1, 2, 3]
>>>> L[1:1] = ['foo', 'bar']
>
> What do you call ``L[1:1]``?
I call that a collection slot.
> I call that a target (and in fact, so do the Python docs).
Targets encompass both names and slots.
--
http://mail.python.org/mailman/listinfo/python-list
-->
Previous 10