The Zen of Python

The Zen of Python, by Tim Peters

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.
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

. . .

Explicit is better than implicit

. . .

{'userId': 1,
 'id': 1,
 'title': 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit',
 'body': 'quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto'}

Simple is better than complex

. . .

Readability Matters

Curly Bracket Languages: Define code blocks using { and }. (Some also define the end of a statement using ;) (aspects of semantics are defined by syntax)

Examples: C, Javascript, R

Offsides Rule Languages: Define code blocks using indentation.

. . .

R: (Curly Brackets + Indentation)

if(a > b) {
    print("a is greater than b")
} else {
    print("a is not greater than b")
}

Python (Indentation)

if a > b:
    print("a is greater than b")
else:
    print("a is not greater than b")

Readability matters

. . .

Clear (Javascript Example):

// This function takes a name and logs
// a string which greets that name
function sayHi(name) {
    console.log("Hi " + name + ", nice to meet you.")
}

sayHi("Sam");

. . .

Not Clear (“Minified” Javascript):

function f(o){console.log("Hi "+o+", nice to meet you.")}f("Sam");

Readability matters

. . .

General Rules:

  1. Make your code easy to read.
  2. Include lots of comments
  3. Keep lines of code short
  4. Avoid single character variable names.
  5. Call your functions with named parameters where applicable.
  6. Use descriptive variable names.

Simple is Better than Complex

. . .

Don’t overcomplicate your code to look smart.

. . .

2nd place, Best ‘The Perl Journal’, 5th Annual Obfuscated Perl Contest: Mark Jason Dominus.

@P=split//,".URRUUxR";@d=split//,"\nlanruoJ lreP ehT";
sub p{@p{"r$p","u$p"}=(P,P);pipe"r$p","u$p";$p++;($q*=
2)+=$f=!fork;map{$P=$P[$f|6&ord$p{}];$p{}=/$P/i?$P:
close}%p}p;p;p;p;p;map$p{}=~/[P.]/&&close,%p;wait
until;map/r/&&<>,%p;print$d[$q]

Sparse is Better than Dense

. . .

Don’t try to stick too much code on one line

. . .

Dense

if i>0: return sqrt(i)
elif i==0: return 0
else: return 1j * sqrt(-i)

. . .

Sparse

if i > 0:
    return sqrt(i)
elif i == 0:
    return 0
else:
    return 1j * sqrt(-i)

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

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

Different languages implement prefix (++i and --i) and postfix (i++ and i--) operators differently, which often leads to confusion.

. . .

Example C code:

int i = 4;
int j = 21;

int k = ++i * 7 + 2 - j--;

What are the values of i, j, and k?

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

There are no prefix or postfix operators in python. You must increment and decrement variables directly.

. . .

Example C code:

int i = 4;
int j = 21;

int k = ++i * 7 + 2 - j--;

What are the values of i, j, and k?

. . .

Errors should never pass silently

. . .

Example: While calling a low-level function that saves data to disk, the function runs out of disk space before all of its data is written.

. . .

Three “Philosophies” of Error Handling

  1. Don’t check for problems.
  2. Systematically determine the Error Status of every statement
  3. Raise Exceptions when things go wrong

Strategy 1: No Error Handling

While saving data to disk, the function runs out of disk space before all of its data is written.

. . .

def write_data(data):
    os.write(data)

. . .

this_is_fine.jpeg

Strategy 1: No Error Handling

While saving data to disk, the function runs out of disk space before all of its data is written.

. . .

Pros:

  • Easy to implement!
  • Usually fine; errors are very rare

. . .

Cons:

  • Unpredictable failure modes
  • Errors can impact other data, programs, etc…
  • Very dangerous when moving to new contexts

Strategy 2: All Functions/Operations return an Error Status (C, C++, Swift)

While saving data to disk, the function runs out of disk space before all of its data is written.

. . .

def write_data(data):
    return_val = os.write(data)
    if return_val == "SUCCESS":
        return "SUCCESS"
    else:
        return "OH-NO!"
    

. . .

error_handling.jpg

Strategy 2: All Functions/Operations return an Error Status (C, C++, Swift)

While saving data to disk, the function runs out of disk space before all of its data is written.

. . .

Pros:

  • Comprehensive coverage of error conditions
  • State of the program is always known

. . .

Cons:

  • High burden on programmers to constantly check error state
  • Any coverage gaps in error checking can lead to unstable conditions and make programs crash-prone

Strategy 3: Errors are handled using an Exception framework

While saving data to disk, the function runs out of disk space before all of its data is written.

. . .

def write_data(data):
    try:
        os.write(data)
    except DiskFullError:
        print("Can't write this file; it's too big!")

. . .

exception.jpeg

Strategy 3: Errors are handled using an Exception framework (Python, Ruby, )

. . .

Pros:

  • Elimintaes repetitive nature of error checking
  • Allows errors to propagate up from lower-level programs, making centralized error handling easier. (tryexcept clauses)

. . .

Cons:

  • Exceptions are easier to ignore/trap than errors.
  • Can lead to sloppy code that “hides” excpetions instead of “handling” them.
  • These cons can be avoided with good programming practice (errors of implementation, not errors of design)

Try or Try Not``

Python Errors

There are two types of errors in Python: SyntaxErrors and Exceptions.

SyntaxErrors

A SyntaxError happens when the Python language interpreter (the parser) detects an incorrectly formatted statement.

This code is trying to divide two numbers, but there are mismatched parentheses. What happens when we run it?

>>> print( 5 / 4 ))
  Cell In[1], line 1
    print( 5 / 4 ))
                  ^
SyntaxError: unmatched ')'

When python says SyntaxError, you should read this as I don't know what you want me to do!?

Exceptions

An Exception happens the code you have written violates the Python language specification.

This code is trying to divide zero by 0. Its syntax is correct. But what happens when we run it?

>>> print( 0 / 0 )
It didn't work because you tried to divide by zero

When python says anything other than SyntaxError, you should read this as You are asking to do something I can't do

In this case, the ZeroDivisionError is raised because the Python language specification does not allow for division by zero.

Types of Exceptions

Python has a lot of builtin Errors that correspond to the definition of the Python language.

. . .

A few common Exceptions you will see include TypeError, IndexError, and KeyError.

TypeError

A TypeError is raised when you try to perform a valid method on an inappropriate data type.

. . .

IndexError

An IndexError is raised when you try to access an undefined element of a sequence. Sequences are structured data types whose elements are stored in a specific order. A list is an example of a sequence.

. . .

---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[14], line 3
      1 # IndexError Example:
      2 my_list = ['a', 'b', 'c', 'd']
----> 3 my_list[4]

IndexError: list index out of range

KeyError

A KeyError is raised when you try to perform a valid method on an inappropriate data type.

. . .

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
Cell In[16], line 4
      1 # KeyError Examples:
      3 my_dict = {'column_1': 'definition 1', 'another_word': 'a second definition'}
----> 4 my_dict['column1']

KeyError: 'column1'

Deciphering Tracebacks

When an exception is raised in python the interpreter generates a “Traceback” that shows where and why the error occurred. Generally, the REPL has most detailed Traceback information, although Jupyter Notebooks and iPython interactive shells also provide necessary information to debug any exception.

---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[17], line 7
      4     print(results)
      6 # calling the function
----> 7 multiply(10, 2)

Cell In[17], line 4, in multiply(num1, num2)
      2 def multiply(num1, num2):
      3     result = num1 * num2
----> 4     print(results)

NameError: name 'results' is not defined