S
Steven D'Aprano
The *formal* argument *is* a name, and that's what the phrase "changes
to the arguments within the called procedure" is talking about.
If you equate "arguments within the called procedure" to the *name* of
the arguments, then changing the arguments would mean changing the NAME,
not the object bound to the name. That is, something like this:
def foo(x):
y = x
del x
except as a single operation. I'm sure that's not what you intended to
say, but that's what you have said. Except for del and rebinding, Python
level code does not allow you to do anything to *names*, only to objects.
But I'm sure you know this, which makes your claim all the more confused.
More to the point, it sees the *name x* rather than the name y, and
rebinding the name x doesn't change the binding of name y. Therefore,
the name y has been passed by value, not by reference.
The term for what you have just said is "non sequitor", Latin for "it
does not follow". The _name_ y is not passed AT ALL. If there is a value
that is passed, it is the object bound to y, and not any name at all.
If you equate "value" with "object", as you suggested some posts ago,
then it could be argued that Python is call-by-value (for value=object)
but because "call by value" has connotations and implications that do not
apply to Python, we prefer to avoid the misleading and confusing term
c-b-v in preference to Barbara Liskov's term "call by sharing" or "call
by object".
At least some sections of the Java community seem to prefer a misleading
and confusing use of the word "value" over clarity and simplicity, but I
for one do not agree with them.
[1] You can pass a string representing the name to a function, which
can then use some combination of setattr, globals(), exec etc to work
with the name represented by that string.
This would be the Python equivalent of the strategy used in C to emulate
call-by-reference -- and it's needed for the same reason, i.e. the
language itself only provides call-by-value. So you pass a value that
you can manually dereference to get the same effect.
In the general case, you can't emulate call-by-reference by passing a
name, because you don't know what the name of an object is. Obviously I
can hard-code some names:
def swap(x, y):
# arguments x and y are actually pointless
g = globals()
g['x'], g['y'] = g['y'], g['x']
but you can't emulate c-b-r's ability to swap arbitrary names determined
by the compiler. The reason is that when you call a function with an
argument, the function sees only the object, and the object does not know
what name(s) is bound to it.
You could do this:
def swap(x_name, y_name):
g = globals()
g[x_name], g[y_name] = g[y_name], g[x_name]
which gives you something a little closer to c-b-r, but it still isn't
the same thing. Some major differences:
- you can't affect values unless they are bound to a name, that is no
swapping of anonymous values, e.g. swap(a[4], a[8]) could not work;
- within a nested scope, you can't affect anything unless it is in the
global scope;
- you need to know the name to apply at runtime, there is no way to
programmatically discover it.
The third point is the most telling. Consider the Pascal procedure:
procedure swap(var x, var y: integer):
var tmp: integer;
begin
tmp := x;
x := y;
y := tmp;
end;
Given two integer variables a and b, you call the procedure swap(a, b),
and the compiler can determine what memory addresses are used. If Pascal
was like Python, you would have to determine the addresses yourself:
a := 1;
b := 2;
swap(12693024, 190342874);
after which a would equal 2 and b would equal 1. Obviously Pascal is not
like that, but Python is (using names instead of memory locations). This
proves that Python names are nothing like Pascal call-by-reference
arguments. Passing a name is *not* Python's way to emulate call-by-
reference.