34

I am learning Python and am intrigued by the following point in PEP 20 The Zen of Python:

There should be one-- and preferably only one --obvious way to do it. Although that way may not be obvious at first unless you're Dutch.

Could anyone offer any concrete examples of this maxim? I am particularly interested in the contrast to other languages such as Ruby. Part of the Ruby design philosophy (originating with Perl, I think?) is that multiple ways of doing it is A Good Thing. Can anyone offer some examples showing the pros and cons of each approach. Note, I'm not after an answer to which is better (which is probably too subjective to ever be answered), but rather an unbiased comparison of the two styles.

Paul
  • 103
  • 3
Charles Roper
  • 1,363
  • 1
  • 13
  • 14

4 Answers4

51

Compared to languages like Perl, Python has a limited number of control constructs:

  • only if and no unless,
  • only for that iterates over sequences and no foreach or C-style for,
  • only while that checks a condition every loop and no do-while,
  • only if-elif and no switch,
  • there's only one comment construct, the #, and for every line you can tell if it is commented out or not, without looking at previous lines.

Also, there's nearly one way to indent your source; most cases of creative indentation are syntactically excluded.

This makes parsing a Python source easier for humans.

There are attempts to be minimal-but-complete in built-in types and the standard library.

  • for mutable list you use the only built-in list type; it's O(1) for most operations, and you never have to choose the right implementation,
  • for immutable lists, equally, you just use the tuple type,
  • for maps, you use the only built-in dict which is damn efficient in most cases, no need to ponder which implementation to use.

Python 3 extends this to integers: no matter how big your integer is, you use the same type and never care about coercion.

Python tries to avoid syntactic sugar. But sometimes it adds syntactic sugar just to make the obvious way obvious. You can write if foo is not None instead of if not (foo is None) because 'is not' is special-cased. Still foo is not None reads easily, can't be misinterpreted, and you don't have to think, you just write the obvious thing.

Of course, most of more complex things in Python can be done in several ways. You can add methods to classes by declaration or by simple slot assignment, you can pass arguments to functions in a number of creative ways, etc. That's just because the internals of the language are mostly exposed.

The key is that there's always one way which is intended to be the best, the cover-all case. If other ways exist, they were not added as equal alternatives (like if and unless) but merely expose the inner workings. Slowly but steadily such alternatives are obsoleted (not eliminated!) by enhancing the known best mechanism.

Decorators wrap AOP function calls. Before 2.6 you had to use __metaclass__ magic member to declare a class's metaclass; now you can use the same decorator syntax for this, too. Prior to 3.0 you had two sorts of strings, byte-oriented and Unicode, which you could inadvertently mix. Now you have the only Unicode str and the only binary-transparent bytes, which you can't mix by mistake.

9000
  • 24,162
  • 4
  • 51
  • 79
  • 4
    Just as a note, don't forget `"""` comments (docstrings). These span multiple lines. – asthasr Jul 27 '11 at 20:08
  • 9
    Triple-quoted literals are just strings, the same as single-quoted, but can span multiple lines without escaping line ends. A string literal right after declaration is considered a doc string, and it's not a comment, it's usually accessible as `__doc__` attribute. But strings is an area where Python definitely provides many 'right ways': use single, double, or triple quote, implicitly join adjacent literals, use `r` for raw literals, etc. – 9000 Jul 27 '11 at 21:29
  • 3
    I think @syrion's comment was regarding the "you can always decide whether a line is commented or not by just looking at it", which is not true because of """ strings. – blubb Aug 01 '11 at 18:25
  • 2
    "This makes parsing a Python source easier for humans." <- that's subjective – jiggy Aug 01 '11 at 19:51
  • 1
    How did the metaclass declaration change in 2.7? Can't find the decorator pattern in the 2.7 docs for metaclasses. – Nick T Sep 03 '13 at 07:20
  • 1
    @NickT: [class decorators](http://www.python.org/dev/peps/pep-3129/) were actually [introduced in 2.6](http://docs.python.org/2.7/whatsnew/2.6.html#pep-3129-class-decorators), not 2.7 as I stated. Thank you for noting that. I've corrected my answer. – 9000 Sep 03 '13 at 14:13
  • Perl has only arrays, and hashes (dicts). There are no tuples. There are even *fewer* ways to do it. Moreover, tuples aren't really immutable because they can have mutable types. `a = ("foo","bar",[4,5]); a[2] += [5,6]` – Evan Carroll Oct 10 '18 at 16:59
  • @EvanCarroll Well, what did you expect? Should every object inside a tuple become immutable? The tuple *itself* is, that's the point. – Oliphaunt Feb 04 '21 at 07:20
10

Another couple of examples are:
len() is a function instead of a method present in every sequence; if you compare with Java you have .length, .size(), .getSize(), and other methods to find the number of elements in a sequence.

Another example is the fact that .join() is a string method, not a method present in every sequence. You don't need to know if the join parameter is a list, a set, a comprehension, or whatever, it will work.

LYF
  • 3
  • 2
Vito De Tullio
  • 559
  • 3
  • 5
8

In C there are many possible ways to increase the value of a variable by one:

i++     // Post-increment, returns the number before the increment
++i     // Pre-increment, returns the number after the increment
i += 1 

Each ends up increasing the value of i by 1, but each is slightly different.

In Python, there's really only one way; just add one.

i += 1

And while there's more than one syntactically valid way to do this (e.g. i = i + 1), you're doing the same thing with the same side effects.

  • 1
    I'm no expert, but that example seems to precisely violate the "only one way to do it" idea. We have two ways to do it, but which is the more obvious? To my eyes the first example is more obvious while the second is a little more terse but no less readable or obvious to any programmer who has progressed beyond the very basics. Thank you for your answer - it's good food for thought. – Charles Roper Jul 27 '11 at 18:34
  • @Peter (and @Charles): Actually, `i = i + 1` is assignment, not an increment. In python an increment is `i += 1`. In C-style languages you can write `i++`, `++i`, and `i += 1`. – Josh K Jul 27 '11 at 18:56
  • 2
    Not sure about your "lot of confusion" comment, all three of your C examples (you missed `i += 1`, BTW) produce exactly the same result. The only time I see people getting confused is when they pre- or post-increment a variable as part of a larger expression, and that's usually quickly rectified by reading the appropriate section of the language reference. Personally, I'd have picked on the fact that you can refer to the fifth character of a string by both `str[4]` or `*(str+4)`, but maybe that was too easy... – TMN Jul 27 '11 at 19:18
  • 3
    @TMN: some cases, like `max(i++, ++i)` can't be quickly rectified. C has a lot of "undefined" and "implementation-dependent" behavior cases, all for a good reason — but each can create a pitfall. – 9000 Jul 27 '11 at 21:33
  • 1
    @TMN: Not to mention 4[str] (valid in C, may not be valid in C++). – Vatine Jul 28 '11 at 11:52
  • Made an edit which I think makes my point a little clearer - but all above comments raise valid points and @9000's answer is much better :) –  Jul 28 '11 at 14:16
  • 1
    @Vatine: Good one! My C's a little rusty, I'd forgotten you could do that. – TMN Jul 28 '11 at 14:59
  • 1
    `i += 1` calls `i.__iadd__` where `i = i + 1` sets `i = i.__add__(1)` for any user-types this is totally implementation defined. – Evan Carroll Oct 10 '18 at 17:02
6

Another possibility might be list comprehensions. In Python, you could do this:

new_list = []
    for item in list_of_items:
       if item < 10:
           new_list.append(item)

But the "obvious" way (if you're Dutch or are more familiar with Python) of doing this would be with a list comprehension:

new_list = [item for item in list_of_items if item < 10]

It's shorter, the new_list is created in one step, it runs faster I believe, and it is elegant. On the downside, one could argue it feels less explicit, but I think once you get used to it, it is just as explicit.

Inca
  • 1,534
  • 10
  • 11
Chelonian
  • 413
  • 3
  • 9
  • For indentation and code: prepend it with 4 spaces, then it will respect indentation. – Inca Aug 01 '11 at 17:33