The Zen of Python

don't write C with Python

2017-04-25

by

Kurt Neufeld

The Zen of Python

Originally written by Tim Peters

Colorful commentary by yours truly

The Zen of Python

In order to help you write Pythonic code, please ponder the following:

* Beautiful is better than ugly. * Explicit is better than implicit. * Simple is better than complex. * Complex is better than complicated. * Flat is better than nested. * Sparse is better than dense. * Readability counts. * Special cases aren't special enough to break the rules. * Although practicality beats purity. * Errors should never pass silently.

The Zen of Python

* Unless explicitly silenced. * In the face of ambiguity, refuse the temptation to guess. * 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. * Now is better than never. * Although never is often better than *right* now. * If the implementation is hard to explain, it's a bad idea. * If the implementation is easy to explain, it may be a good idea. * Namespaces are one honking great idea -- let's do more of those!

Beautiful is better than ugly

No shit.

Beautiful is better than ugly

Whitespace helps significantly, don't be afraid to put empty lines around blocks: `try/except`, `while`, `for`, `if/else`, `return`

Beautiful is better than ugly


      def update(self, orig_entries, new_text):
          new_entries=self._parse(new_text)
          if new_entries:
            new_entries=new_entries[1:]
          else:
            return False,False,False
          start_set=set([e.pk for e in orig_entries])
          end_set=set([e.pk for e in new_entries])
          self.del_entries(orig_entries)
          self.add_entries(new_entries)
          deleted=len(start_set.difference(end_set))
          updated=len(end_set.intersection(start_set))
          created=len(end_set.difference(start_set))
          return deleted, updated, created
      

Beautiful is better than ugly


      def update(self, orig_entries, new_text):
          new_entries = self._parse(new_text)

          if new_entries:
            new_entries = new_entries[1:]
          else:
            return False, False, False
          
          start_set = set([e.pk for e in orig_entries])
          end_set = set([e.pk for e in new_entries])

          self.del_entries( orig_entries )
          self.add_entries( new_entries )

          deleted = len(start_set.difference(end_set))
          updated = len(end_set.intersection(start_set))
          created = len(end_set.difference(start_set))

          return deleted, updated, created
      

Explicit is better than implicit

Avoid _magic_. Use Ruby if you like that nonsense.

One example is, never `import *`. You need to read code many more times than you write it, so be explicit: `from foo import bar, baz`.

Plus you won't break your code linter so your editor can tell you when you have a typo or a non-defined object.

Simple is better than complex

It's often possible to do one _thing_ in one line of python. But it's often better to use an intermediate variable for clarity.

Complex is better than complicated

I prefer to think of this kone as _sophisticated_ vs _complicated_.

A binary search is more sophisticated than a linear search but it's not particularly complicated.

Flat is better than nested

Some overly pedantic bean counter once said that a function should only ever have one return value because having more than one increases the cyclomatic complexity.

Whatever the hell that is.

The same pedantic bean counter will also be very soup nazi about 80 columns max line length. Then you end up with monstrosities like the following:

Flat is better than nested


def some_function_that_probably_has_too_long_a_name(variable_a, variable_b,
                                                    variable_c, variable_d):

    return_value = None
    
    if variable_a:
        if variable_b:
            if variable_c:
                if variable_d:
                    return_value = another_really_long_function(variable_a+1,
                                                                variable_b+2,
                                                                variable_c+3,
                                                                variable_d+4
                                                                )
                    if return_value:
                        oh_thank_goodness_that_worked()
                else:
                    return_value = False
            else:
                return_value = False
        else:
            return_value = False
    else:
        return_value = False

    assert return_value is not None
    return return_value
      

Flat is better than nested

Such low cyclomatic complexity!

Much Wow!

Flat is better than nested

Same but better


def reasonable_name(variable_a, variable_b, variable_c, variable_d):

    if not all(variable_a, variable_b, variable_c, variable_d):
        return False

    if another_func(variable_a+1, variable_b+2, variable_c+3, variable_d+4)
        totes_wurkz()

    return True
      

_to be fair you see that crap more in C/C++/Java_

Sparse is better than dense

See whitespace example above.

Readability counts

It's a common misconception that people write code for computers. You should always write code for other people since computers don't care what it looks like.

Let loose your inner rebel and merely _aim_ for 80 character width, it's totally okay to go over 80 characters if it's easier to read.

WTF is with 80 characters anyways? Because that was a hard limit we had 40 years ago!?!

Special cases aren't special enough to break the rules

Some of these rules are a little self contradictory, I urge you to do some deep meditation on this stuff

While listening to _Sounds of the Mountain Streams in Autumn_

Some form of incense is mandatory

Although practicality beats purity

But don't go too far off the tracks.

Errors should never pass silently

For the ever loving baby jesus don't do this:


      try:
          some_function_that_raises()
      except SomeException:
          pass
      

Errors should never pass silently

Or the 1000x worser:


      try:
          some_function_that_raises()
      except:
          pass
      

Errors should never pass silently

In fact, please run the following on all your code and then fix:


      grep -r 'except:' .
      

Errors should never pass silently

This is totally fine _(albeit weird)_:


      try:
          some_function_that_raises()
      except Exception as e:
          logger.error("we're ignoring this error: %s", e)
      

Unless explicitly silenced


      with ignore(SomeException):
          some_function_that_raises()
      

In the face of ambiguity, refuse the temptation to guess

But when you do, leave a comment.

There should be one-- and preferably only one --obvious way to do it

See perl or twisted on why it sucks if there's 13 ways to do anything and everything.

Although that way may not be obvious at first unless you're Dutch

I'm not even Dutch enough to get that joke/reference

Now is better than never

Look up [Perfect is the Enemy of Good](https://www.psychologytoday.com/blog/happiness-in-world/201106/why-perfect-is-the-enemy-good) by Alex Lickerman

See also _Release Early, Release Often_.

Although never is often better than *right* now

Because _right now_ usually means you're under pressure and you'll ship something terrible that you'll have to support it forever.

If the implementation is hard to explain, it's a bad idea

See monads.

Actually monads sound pretty cool for the 30 seconds I can understand and remember them.

If the implementation is easy to explain, it may be a good idea

But it's no guarantee.

Namespaces are one honking great idea -- let's do more of those!

They _are_ a honking great idea. See what happens when you don't have them in PHP or C.

Don't be afraid to use them _in_ your code, ie: you don't have to import every function that you use.

Namespaces are one honking great idea -- let's do more of those!


      import os

      print(os.path.abspath(__file__))
      

The End

questions?