Python Design Principles

lechequier at gmail.com lechequier at gmail.com
Fri Sep 9 14:41:29 EDT 2005


In a previous post, I asked about the inconsistency in usage patterns
in operating on mutable and immutable types. Thanks Dave and everyone
else for answering my question so thoughtfully and helping me to
understand the reasoning about why the different usage patterns are not
deemed to be inconsistent.

But I am still puzzled by the argument that has been given for why
methods that operate on mutable types should return None, namely, that
the designers of python didn't want the users to shoot themselves in
the foot by thinking a method simply returned a result and left the
data structure unchanged.

In the context of fundamental design principles, if you asked a random
sample of Python gurus what is more Pythonesque: preventing users from
shooting themselves in the foot or making things easier to accomplish,
my impression is that people would overwhelmingly choose the latter.
After all, the fact that Python is not strongly typed and is
interpreted rather than compiled gives plenty of ways for people to
shoot themselves in the foot but what is gained is the abilitity to do
more with less code.

But in this instance, by not allowing operations on mutable types to
return the mutated objects, it seems that the other side is being
taken, sacrificing programmer producitivity for concerns about
producing possible side effects. It is somewhat ironic, I think, that
Java, a language whose design principles clearly side on preventing
users from shooting themselves in the foot, much more so thatn Python,
generally allows you to get back the mutated object.

I'm not trying to change minds here but just to understand better how
this particular design decision fits into Python's overall design
principles.

thanks,
Scott

Dave Benjamin wrote:
> 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