Question about consistency in python language

Dave Benjamin ramen at lackingtalent.com
Thu Sep 8 20:55:32 EDT 2005


lechequier at gmail.com wrote:
> Let's say I define a list of pairs as follows:
> 
>>>l = [('d', 3), ('a', 2), ('b', 1)]
> 
> 
> Can anyone explain why this does not work?
> 
>>>h = {}.update(l)
> 
> 
> and instead I have to go:
> 
>>>h = {}
>>>h.update(l)
> 
> to initialize a dictionary with the given list of pairs?
> 
> when an analagous operation on strings works fine:
> 
>>>s = "".join(["d","o","g"])
> 
> 
> Seems inconsistent. 

Python is actually quite consistent in this regard: methods that modify 
an object in-place return None; methods that do not modify an object 
in-place return a new object instead. Since strings are immutable, they 
cannot be modified in-place, so it makes sense for the "join" method to 
return a new string. On the other hand, Python's dictionaries are 
imperative-style and so most operations on a dictionary modify an 
existing dictionary.

I was initially bothered by the phenomenon of so many methods returning 
None because they could not be chained. But I have come to deeply 
appreciate this style for a couple of reasons. First, it makes it clear 
which methods are side-effecting (like "update") and which are not (like 
"sort").

Second, it is not always clear what a good return value is for a 
mutator. Perhaps {}.update() should return the dictionary, making 
chaining convenient. Perhaps it should return the total number of items 
after updating. Or maybe it should return the number of new keys that 
were added, or a list of those keys. All of these are plausible 
behaviors; the problem is that "update" is not a function. Its job is to 
change something, not return something. Any possible return value would 
be a convenience for certain tasks and useless for other tasks.

It's also hard to remember, in my opinion. For example, JavaScript has a 
"push" method on the Array object which behaves like Python's "append" 
method on lists:

js> var a = [];
js> a.push(5);
1
js> a.push(6);
2

I bet you that almost nobody knows that "push" returns the new length of 
the Array. It could just as easily have returned "a" here. I could 
always write "a.length", if I really needed to know the new length. This 
sort of thing becomes language trivia, and when I write in JavaScript I 
always ignore the result of "push" because, even if *I* know what it 
returns, chances are that my readers don't.

Another reason that Python adopts the convention of returning None for 
mutators is that it discourages the use of side-effecting code in 
expressions. Mixing side-effects with expressions can lead to code that 
is hard to read and understand. This is often debated by those who know 
better and wish to write things like "h.update(a).update(b)" (method 
chaining) or "while (line = file.readline()): ...". Python's decision is 
pretty clear, and it's also evident in the division between statements 
and expressions.

Regardless of whether you like Python's style decision or not, if you 
dig deeper I think you will find that it is pretty consistent, and that 
there are useful benefits to Python's way of handling side effects.

Dave

PS. If mutators had to return a value, I'd have them return "self", 
probably 95% of the time. But then, it wouldn't be Python anymore. It'd 
be Ruby, maybe.



More information about the Python-list mailing list