classification
Title: Python violates most users expectations via the modification differences of immutable and mutable objects in methods
Type: behavior Stage:
Components: Versions:
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: Theo.Julienne, asdfasdfasdfasdfasdfasdfasdf, benjamin.peterson, giampaolo.rodola, loewis, r.david.murray, rafe.kettler, rhettinger
Priority: normal Keywords:

Created on 2010-08-27 13:53 by asdfasdfasdfasdfasdfasdfasdf, last changed 2010-10-30 00:28 by rafe.kettler. This issue is now closed.

Messages (21)
msg115074 - (view) Author: david (asdfasdfasdfasdfasdfasdfasdf) Date: 2010-08-27 13:53
Python violates most users expectations via the modification differences of immutable and mutable objects in methods.

def foo(bar):
    bar = bar + bar

def listy(bar):
    bar =  [1]

def dicty(bar):
    bar['1'] = '1'

if __name__ == "__main__":
    bar = 1
    foo(bar)
    print bar
    baz = []
    print baz
    listy(baz)
    print baz
    dict_d = {}
    print dict_d
    dicty(dict_d)
    print dict_d

this will output
1
[]
[]
{}
{'1': '1'}


So sure this is 'expected'(pass by reference vs new object - for immutable objects) but it sure isn't obvious.
I feel this is a bug in python core.
I think that the behaviour should be the same for *all* objects.
If it is pass by reference, *and* the item has to be able to be updated(I feel this breaks most people's expectations...) then the result of a modification to an object that is immutable should be that the pointer to the original now points to the resulting string. 

Personally I do not want to be able to modify the dictionary as I did above like I did.
msg115076 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2010-08-27 14:09
> I feel this breaks most people's expectations...

I think you are quite mistaken in this assumption. Sure, object references are difficult to grasp at first, but they are a highly useful concept, and follow very simple, systematic principles - you just need to "get" it once.

> result of a modification to an object that is immutable

Objects that are immutable *cannot* be modified - that this the very definition of "immutable". It seems that your understanding of Python still doesn't match its semantics.

However, Python really can't adjust to whatever your current understanding is: most people (dealing with it) already have the right understanding.

So closing this as "won't fix".
msg115085 - (view) Author: david (asdfasdfasdfasdfasdfasdfasdf) Date: 2010-08-27 14:53
I strongly suggest you reconsider as *most* programmers will not think about it this way.
No you failed to understand my bug report apparently. I understand the behaviour. However, you failed to understand the problem. 

*PLEASE* read and think about it.
msg115087 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2010-08-27 15:07
What you describe is the way Python is *designed* to work at a very fundamental level.  Read about 'namespaces', and look at the computer science literature on 'call by object'.
msg115088 - (view) Author: david (asdfasdfasdfasdfasdfasdfasdf) Date: 2010-08-27 15:11
def list_again(foo):
	foo.append("bar")

def list_again_again(foo):
	foo = foo + ["1"]

if __name__ == "__main__":
	bar = []
	list_again(bar)
	print bar
	list_again_again(bar)
	print bar



Ok so let me without running the above code exactly what it does AND then, stop and think about how *most* coders expect it to behave.
msg115089 - (view) Author: david (asdfasdfasdfasdfasdfasdfasdf) Date: 2010-08-27 15:13
In c pointers are *explicit*, ditto in c++, in java everything is a pointer.
In asm, well that is asm.
This behaviour in python, makes python code *really* hard to read and *hard* to understand. 

Can you python devs / people stop calling a bug reporter stupid when they point out language flaws?
msg115090 - (view) Author: david (asdfasdfasdfasdfasdfasdfasdf) Date: 2010-08-27 15:18
If you like I can look for this new security bugs in existing python projects and show you why this is a *very* bad idea. 

Please stop this python isolated mentality and autistic behaviour and consider the possibility of being wrong.
msg115092 - (view) Author: Benjamin Peterson (benjamin.peterson) * (Python committer) Date: 2010-08-27 15:25
Please use a language that makes sense to you then.
msg115094 - (view) Author: david (asdfasdfasdfasdfasdfasdfasdf) Date: 2010-08-27 15:28
Excuse me for reporting weird and not expected behaviour on behalf of *most* coders.

Here https://bugs.edge.launchpad.net/ubuntu/+source/checkbox/+bug/625076
I understand python fine. If I have to find security bugs in *lots* more python projects to prove my point I will do this *to* make this point really clear. 
You (responders so far) seem to not understand the nature of this problem and you are simply calling me stupid.
msg115095 - (view) Author: david (asdfasdfasdfasdfasdfasdfasdf) Date: 2010-08-27 15:32
Just to clarify that last comment. 
By exhibiting this behaviour python, introduces the potential for a lot more errors in code that seems to be correct to most people. 
Remember this bug is about the differences in behaviour for 'mutable' and 'immutable' objects.
msg115096 - (view) Author: Benjamin Peterson (benjamin.peterson) * (Python committer) Date: 2010-08-27 15:34
2010/8/27 david <report@bugs.python.org>:
>
> david <db.pub.mail@gmail.com> added the comment:
>
> Excuse me for reporting weird and not expected behaviour on behalf of *most* coders.

Your assumption that most coders are confused by this comes from where?

>
> Here https://bugs.edge.launchpad.net/ubuntu/+source/checkbox/+bug/625076
> I understand python fine. If I have to find security bugs in *lots* more python projects to prove my point I will do this *to* make this point really clear.

Well, that would be relevant to those projects, not python.

> You (responders so far) seem to not understand the nature of this problem and you are simply calling me stupid.

Actually, the only insult that seem to have happened is you calling us
"autistic".

Also, repeatedly opening this bug report will not make it any more
likely that action will be taken on it.
msg115098 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2010-08-27 15:36
The current design has existed for almost twenty years.
It is deeply embedded in the language.   For better or
worse, it cannot and won't change.
msg115099 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2010-08-27 15:39
david, please accept that regardless of whether this is a bug or not, people dealing with bug reports will always determine that it is no bug, and close it. *Please stop reopening this report*.

If you want to appeal to this decision, please post a message to python-ideas, asking for support from other people.

Also be prepared to write a PEP proposing changes to the language to make it work more like you expect. Please expect that this PEP will be very difficult to write, and, if you indeed manage to come up with a sound proposal, that proposal will get a lot of opposition.
msg115101 - (view) Author: david (asdfasdfasdfasdfasdfasdfasdf) Date: 2010-08-27 15:42
"Please stop this python isolated mentality and autistic behaviour and consider the possibility of being wrong."
... No I didn't, did you read what I said?
Also, repeatedly closing this bug isn't going to make it go away.
You are kidding your self if you think that 99% of python coders will be able to tell me the correct answers to the code output I have pasted here(without running it). 


Saying something is correct because of history is *just* plain wrong.
I will happily write a PEP and anything else required to get this changed.
msg115102 - (view) Author: david (asdfasdfasdfasdfasdfasdfasdf) Date: 2010-08-27 15:43
To quote the zen of python:
"Readability counts.
Special cases aren't special enough to break the rules."
msg115103 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2010-08-27 15:44
<Yawn>
msg115141 - (view) Author: Theo Julienne (Theo.Julienne) Date: 2010-08-27 23:10
def list_again(foo):
	foo.append("bar")

def list_again_again(foo):
	foo = foo + ["1"]


The part that can be confusing is that 'foo' is a *copy* of a *reference* to an object. Because 'foo' is a copy, assigning to it will only alter a local copy, while calling methods on it will always call on the original object (because it's a reference to the same place). When an object is mutable, it just has no methods that modify it, so the only way to change it is by assignment, which will never result in changes to the outside function. There are no special cases, but it does require understanding pass-by-(copy/reference) and object mutability, and is against most people's intuitions.

The way that behaves is actually how most *programmers* would expect (because they are used to it), though. Every other language I can think of does it this way. For example, in C, a pointer is still a copy of a reference -- if you assign to the pointer, it wont propagate up to the caller. The same goes in Java.

Perhaps what makes it harder to understand in Python is that everything is an object and looks the same regardless of mutability, whereas other languages typically have conventions (such as capitalising: int, str, Dict, List) to make it clearer when something is an object (which usually means the same as a python mutable object) as opposed to a built-in type (which usually means the same as a python immutable object).

Just my 2c
msg115147 - (view) Author: david (asdfasdfasdfasdfasdfasdfasdf) Date: 2010-08-28 05:05
On 28 August 2010 09:10, Theo Julienne <report@bugs.python.org> wrote:
>
> Theo Julienne <theo.julienne@gmail.com> added the comment:
>
> def list_again(foo):
>        foo.append("bar")
>
> def list_again_again(foo):
>        foo = foo + ["1"]
>
>
> The part that can be confusing is that 'foo' is a *copy* of a *reference* to an object. Because 'foo' is a copy, assigning to it will only alter a local copy, while calling methods on it will always call on the original object (because it's a reference to the same place). When an object is mutable, it just has no methods that modify it, so the only way to change it is by assignment, which will never result in changes to the outside function.

Yes exactly!

>There are no special cases, but it does require understanding pass-by-(copy/reference) and object mutability, and is against most people's intuitions.

No that isn't really my point here really.

> The way that behaves is actually how most *programmers* would expect (because they are used to it), though. Every other language I can think of does it this way. For example, in C, a pointer is still a copy of a reference -- if you assign to the pointer, it wont propagate up to the caller. The same goes in Java.

Um sort of. This is kind of confusing right see the code below.

> Perhaps what makes it harder to understand in Python is that everything is an object and looks the same regardless of mutability, whereas other languages typically have conventions (such as capitalising: int, str, Dict, List) to make it clearer when something is an object (which usually means the same as a python mutable object) as opposed to a built-in type (which usually means the same as a python immutable object).

I actually think the problem is that python coders are not aware of
this largely (from my experience) and that the *operators* are going
to behave differently (java, for example,  differs in this respect (
you don't '+' maps )).

Here are some examples of use in other languages. Mapping python can
be found some of them.

foo.cpp
#include <iostream>
#include <string>
using namespace std;

void do_More_Foo(int *it);

int main()
{
	int *foo;
	int bar = 0;
	foo = &bar;
	cout << *foo << " "  << foo << endl;
	do_More_Foo(foo);
	cout << *foo << " "  << foo << endl;
	return 0;
}

void do_More_Foo(int *it)
{
	cout << "do more foo " << it << " " << *it <<endl;
	*it = 9;
}

g++ -Wall -Werror foo.cpp  -lmudflap -fmudflap
./a.out
0 0x7fff0ec19adc
do more foo 0x7fff0ec19adc 0
9 0x7fff0ec19adc

AND in python:
#!/usr/bin/env python

def do_More_Foo(it):
	print "in do_More_Foo"
	print "foo", foo, id(foo), id(foo[0])
	print "it", it, id(it)
	it.pop()
	it.append("9")

if __name__ == "__main__":
	bar = 0
	foo = [bar]
	print id(bar)
	print id(foo[0])
	print foo, id(foo)
	do_More_Foo(foo)
	print "after do_More_Foo", foo, id(foo), id(foo[0])

output:
16307968
16307968
[0] 140400602766512
in do_More_Foo
foo [0] 140400602766512 16307968
it [0] 140400602766512
after do_More_Foo ['9'] 140400602766512 140400602799200

However, this isn't what you expect people to do.
Oh and how did foo get there ;) (surprise! python isn't simple is it ?
- note the id).

So I think most people are going to think about this and write it like this:
#!/usr/bin/env python

def woops(it):
	# erh make foo = 9 ?
	print "foo", foo, id(foo)
	foo = 9
	print "foo", foo, id(foo)

def do_More_Foo(it):
	print "in do_More_Foo"
	print "foo", foo, id(foo)
	print "it", it, id(it)
	it = 9
	print "it", it, id(it)

if __name__ == "__main__":
	foo = 0
	print foo, id(foo)
	do_More_Foo(foo)
	print "after do_More_Foo", foo, id(foo)
	woops(foo)
	print "after do_More_Foo", foo, id(foo)

output:
0 19838720
in do_More_Foo
foo 0 19838720
it 0 19838720
it 9 19838504
after do_More_Foo 0 19838720
foo
Traceback (most recent call last):
  File "python-cpp2.py", line 21, in <module>
    woops(foo)
  File "python-cpp2.py", line 5, in woops
    print "foo", foo, id(foo)
UnboundLocalError: local variable 'foo' referenced before assignment

As you expect it is not the foo you are looking for ;)

foo.java

import java.util.HashMap;

import javax.print.DocFlavor.STRING;

public class foo
{

	public void bar(HashMap<String, String> z)
	{

		z.put("foo", "bar");
		HashMap<String, String> b = z;
		b.put("foo2", "ba2r");
	}

	void magic(String b)
	{
		String c = b;
		b = b.replace('b', 'c');
		System.out.println(c);
		System.out.println(b);
	}

	void moremagic(StringBuilder z)
	{
		z.append("b");
	}
	public static void main(String[] args)
	{
		HashMap<String, String> baz = new HashMap<String, String>();
		foo myfoo = new foo();
		myfoo.bar(baz);
		System.out.println(baz.toString());
		String aaa = "b";
		myfoo.magic(aaa);
		System.out.println(aaa);
		StringBuilder sb = new StringBuilder();
		myfoo.moremagic(sb);
		System.out.println(sb);
	}

}
java foo
{foo2=ba2r, foo=bar}
b
c
b
b

#!/usr/bin/env python

def bar(a_dict):
	a_dict["foo"] = "bar"
	new_dict = a_dict
	a_dict["foo2"] = "ba2r"

def magic(string_b):
	c = string_b
	string_b = string_b.replace('b', 'c')
	print c
	print string_b

def moremagic(list_s):
	list_s.append("b")

if __name__ == "__main__":
	d = dict()
	bar(d)
	print d
	aaa = "b"
	magic(aaa)
	print aaa
	a_list = []
	moremagic(a_list)
	print a_list[0]

python python-java.py
{'foo': 'bar', 'foo2': 'ba2r'}
b
c
b
b

- no problems here :)  - but in python we can use an operator like '+'
on a list which introduces the very confusion I am talking about in
respect to the differing behaviour as I stated at the top of this bug
report. :)
msg115150 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2010-08-28 12:34
This is not an appropriate discussion for the bug tracker.  Please take it to the Python mailing list.
msg115151 - (view) Author: david (asdfasdfasdfasdfasdfasdfasdf) Date: 2010-08-28 12:41
On 28 August 2010 22:34, R. David Murray <report@bugs.python.org> wrote:
>
> R. David Murray <rdmurray@bitdance.com> added the comment:
>
> This is not an appropriate discussion for the bug tracker.  Please take it to the Python mailing list.

Fair enough.
One last comment though (here) - I think that  making mutable objects
being immutable outside their immediate scope  (by *default* ) would
be a good solution.
msg115152 - (view) Author: david (asdfasdfasdfasdfasdfasdfasdf) Date: 2010-08-28 12:48
On 28 August 2010 22:41, david <report@bugs.python.org> wrote:
>
> david <db.pub.mail@gmail.com> added the comment:
>
> On 28 August 2010 22:34, R. David Murray <report@bugs.python.org> wrote:
>>
>> R. David Murray <rdmurray@bitdance.com> added the comment:
>>
>> This is not an appropriate discussion for the bug tracker.  Please take it to the Python mailing list.
>
> Fair enough.
> One last comment though (here) - I think that  making mutable objects
> being immutable outside their immediate scope  (by *default* ) would
> be a good solution.

%s/being//
History
Date User Action Args
2010-10-30 00:28:45rafe.kettlersetnosy: + rafe.kettler
2010-08-28 12:48:10asdfasdfasdfasdfasdfasdfasdfsetmessages: + msg115152
2010-08-28 12:41:46asdfasdfasdfasdfasdfasdfasdfsetmessages: + msg115151
2010-08-28 12:34:44r.david.murraysetmessages: + msg115150
2010-08-28 05:05:24asdfasdfasdfasdfasdfasdfasdfsetmessages: + msg115147
2010-08-27 23:10:12Theo.Juliennesetnosy: + Theo.Julienne
messages: + msg115141
2010-08-27 15:44:21rhettingersetmessages: + msg115103
2010-08-27 15:43:52benjamin.petersonsetstatus: open -> closed
resolution: later -> not a bug
2010-08-27 15:43:41asdfasdfasdfasdfasdfasdfasdfsetmessages: + msg115102
2010-08-27 15:42:08asdfasdfasdfasdfasdfasdfasdfsetstatus: closed -> open
resolution: not a bug -> later
messages: + msg115101
2010-08-27 15:39:03loewissetmessages: + msg115099
2010-08-27 15:38:18giampaolo.rodolasetnosy: + giampaolo.rodola
2010-08-27 15:36:28rhettingersetnosy: + rhettinger
messages: + msg115098
2010-08-27 15:35:13benjamin.petersonsetstatus: open -> closed
resolution: later -> not a bug
2010-08-27 15:34:47benjamin.petersonsetmessages: + msg115096
2010-08-27 15:32:32asdfasdfasdfasdfasdfasdfasdfsetmessages: + msg115095
2010-08-27 15:29:05asdfasdfasdfasdfasdfasdfasdfsetstatus: closed -> open
resolution: not a bug -> later
2010-08-27 15:28:06asdfasdfasdfasdfasdfasdfasdfsetmessages: + msg115094
2010-08-27 15:25:35benjamin.petersonsetstatus: open -> closed

nosy: + benjamin.peterson
messages: + msg115092

resolution: later -> not a bug
2010-08-27 15:24:53asdfasdfasdfasdfasdfasdfasdfsettype: behavior
2010-08-27 15:18:00asdfasdfasdfasdfasdfasdfasdfsetmessages: + msg115090
2010-08-27 15:15:37asdfasdfasdfasdfasdfasdfasdfsetresolution: not a bug -> later
2010-08-27 15:15:28asdfasdfasdfasdfasdfasdfasdfsetstatus: closed -> open
2010-08-27 15:14:53benjamin.petersonsetstatus: open -> closed
2010-08-27 15:13:55asdfasdfasdfasdfasdfasdfasdfsetmessages: + msg115089
2010-08-27 15:12:07asdfasdfasdfasdfasdfasdfasdfsetstatus: closed -> open
2010-08-27 15:11:54asdfasdfasdfasdfasdfasdfasdfsetmessages: + msg115088
2010-08-27 15:07:49r.david.murraysetstatus: open -> closed

nosy: + r.david.murray
messages: + msg115087

resolution: later -> not a bug
2010-08-27 15:03:25asdfasdfasdfasdfasdfasdfasdfsetstatus: closed -> open
2010-08-27 14:53:20asdfasdfasdfasdfasdfasdfasdfsetresolution: wont fix -> later
2010-08-27 14:53:05asdfasdfasdfasdfasdfasdfasdfsetmessages: + msg115085
2010-08-27 14:09:20loewissetstatus: open -> closed

nosy: + loewis
messages: + msg115076

resolution: wont fix
2010-08-27 13:53:25asdfasdfasdfasdfasdfasdfasdfcreate