The “does Python have variables?” debate

Steven D'Aprano steve+comp.lang.python at pearwood.info
Thu May 8 20:54:25 EDT 2014


On Thu, 08 May 2014 09:26:57 +0200, Johannes Schneider wrote:

> On 08.05.2014 02:35, Ben Finney wrote:
>> Marko Rauhamaa <marko at pacujo.net> writes:
> [..]
>> Python, on the other hand, has this behaviour::
>>
>>      foo = [1, 2, 3]
>>      bar = foo          # ‘bar’ binds to the value ‘[1, 2, 3]’ assert
>>      foo == bar  # succeeds
>>      foo[1] = "spam"    # ‘foo’ *and* ‘bar’ now == [1, "spam", 3]
> [..]
> 
> IMHO this is the behavior of having a variable pointing to it's value;
> foo to the list and bar to foo.

You're almost right -- foo is a reference to the list, that is, foo 
"points to" the list. But the second part is completely wrong: bar does 
not point at *foo*, it points at the *same list* that foo points to.

What's the difference? Consider this ASCII diagram (best viewed in a 
monospaced font):

foo ------+
          |
          V
bar ---> [1, 2, 3]

If you modify the list, both foo and bar see the same change, because 
they refer to the same list. But if you *assign* to either foo or bar, 
let's say foo:

foo ---> 42
bar ---> [1, 2, 3]


bar continues to refer to the list, while foo has been rebound to a new 
value. That's how Python works. Now, instead, let's suppose bar pointed 
to foo:

bar ---> foo ---> [1, 2, 3]

Both bar's and foo's value is the list, like before. (We suppose that, 
when looking up a name, Python follows the chain of pointers as far as 
needed until it reaches a value.) And now we reassign foo:

bar ---> foo ---> [4, 5, 6, 7]

But since bar still points to foo, bar's value *follows* foo even after 
rebinding foo. bar is an alias for foo. And that is *not* how Python 
works: using the word "alias" to describe foo = bar = [1, 2, 3] is 
misleading. 


> consider the following:
>  >>> def f(l):
> ...     l[1] = 'foo'
> ...
>  >>> l1 =  [1,2,3]
>  >>> f(l1)
>  >>> l1
> [1, 'foo', 3]
> 
> this means, l1 consists of "pointers" to its values. 

That does not follow. It happens to be that CPython does implement lists 
as an array of pointers, but that is a coincidence and is not a 
consequence of what you see. For example, ll might have been a linked 
list of chained tuples (value, next) rather than a built-in list, and you 
would still see the same result.

What you are seeing is that setting an item causes an in-place 
modification, not a rebinding of the name ll. l[1] = 'foo' calls 
l.__setitem__ which mutates the list, it doesn't rebind l.

This confusion is a good demonstration of why the meme that Python 
variables is "just like C pointers" is harmful.


> Otherwise, it's not calling by reference,

But it isn't call by reference. If it were call by reference, you could 
write a swap procedure that swaps the contents of two variables:

a = 1
b = 2
swap(a, b)
print a, b
=> prints 2, 1


You cannot do this in Python! Python has no call by reference.

Please read this for more information:

http://import-that.dreamwidth.org/1130.html



[...snip example...]
> does not change l1. Once again, if I pass an object

But lists ARE objects. All values in Python are objects.


> it behaves like calling by reference:

It certainly does not. The best you can say is that mutation operations 
can, under some circumstances, mimic some of the effects of call by 
reference. For example, instead of using call by reference to get an 
output parameter, like Pascal uses, you can pass an object and mutate the 
object in place so that the caller sees the mutation.



-- 
Steven D'Aprano
http://import-that.dreamwidth.org/



More information about the Python-list mailing list