Workshop 6.

High readability is one of the great things about Python. For example, it forces the programmer to keep correct indentation, making the code automatically readable and not including unnecessary "end" statements.

However, high readability comes not only from the syntax of the language itself, but also from its community and guidelines.

PEP8 guideline with better readability than the python.org one: https://pep8.org/

Examples

Explicit vs Implicit

Which one of the two snippets of code is explicit, which one is implicit?

What is better about the explicit one? What is better about the implicit one? What should your reasoning be when you choose whether to use implicit code or explicit code?

Example #1

In [ ]:
def make_complex(*args):
    x, y = args
    return dict(**locals())
In [ ]:
def make_complex(x, y):
    return {'x': x, 'y': y}

Example #2

In [ ]:
import requests
r = requests.get("https://miguelgfierro.com")
In [ ]:
from requests import *
r = get("https://miguelgfierro.com")

Example #3

In [ ]:
def read(filename):
    if filename[-4:] == '.csv':
        # code for reading a csv
        pass
    elif filename[-5:] == '.json':
        # code for reading a json
        pass    
In [ ]:
def read_csv(filename):
    pass
    # code for reading a csv

def read_json(filename):
    pass
    # code for reading a json

Explicit examples make it clear right away what is happening even if in some cases this leads to redundant information.

Implicit examples assume additional knowledge. This assumption may be correct. It may be incorrect.

It is often easier to write implicit code. When you write code, you have the additional knowledge. When you read code, this may not be the case. This leads to cases when while writing code you compare Implicit and Explicit examples from the perspective of someone with additional knowledge. From this perspective explicit code is redundant.

You reasoning shouldn't be "what is easier to write". It should be "what is easier to read assuming minimal pre-existing knowledge about how the program works". In this case explicit code is much more often the better option.

Simple vs Complex. One statement per line

"Sparse is better than dense"

It may be fun to put everything into a single dense statement. However, it hurts readability.

Splitting code into small simple parts lets the reader easily understand what each part does separately.

In [ ]:
print 'one'; print 'two'

if x == 1: print 'one'

if <complex comparison> and <other complex comparison>:
    # do something
In [ ]:
print 'one'
print 'two'

if x == 1:
    print 'one'

cond1 = <complex comparison>
cond2 = <other complex comparison>
if cond1 and cond2:
    # do something
In [ ]:
# A single statement that is very hard to understand.
print('\n'.join("%i bytes = %i bits which has %i possible values." % (j, j*8, 256**j-1) for j in (1 << i for i in range(8))))
1 bytes = 8 bits which has 255 possible values.
2 bytes = 16 bits which has 65535 possible values.
4 bytes = 32 bits which has 4294967295 possible values.
8 bytes = 64 bits which has 18446744073709551615 possible values.
16 bytes = 128 bits which has 340282366920938463463374607431768211455 possible values.
32 bytes = 256 bits which has 115792089237316195423570985008687907853269984665640564039457584007913129639935 possible values.
64 bytes = 512 bits which has 13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084095 possible values.
128 bytes = 1024 bits which has 179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827237163350510684586298239947245938479716304835356329624224137215 possible values.

Example from Homework 1. Problem #18

In [ ]:
A = int(input())
B = int(input())
print(((((A // B) * A) + ((B // A) * B)) // ((A // B) + (B // A))))
5
8
8
In [ ]:
a = int(input())
b = int(input())

remainder_a = a // b
remainder_b = b // a

max_number = (remainder_a * a + remainder_b * b) // (remainder_a + remainder_b)

print(max_number)
8
4
8

Indentation and whitespace

Use four spaces per indentation level.

You can start a next line inside parentheses, brackets and braces. In this case you are technically free to use any indentation you want. So you should make readability your priority when choosing the indentation.

  • Using vertical alignment makes it easy to see all the arguments of a function at once.
  • Using additional indentation makes it easy to see where the arguments end and the next line of code starts.
In [ ]:
# Aligned with opening delimiter.
foo = long_function_name(var_one, var_two,
                         var_three, var_four)

# More indentation included to distinguish this from the rest.
def long_function_name(
        var_one, var_two, var_three,
        var_four):
    print(var_one)

# Hanging indents should add a level.
foo = long_function_name(
    var_one, var_two,
    var_three, var_four)
In [ ]:
# Arguments on first line forbidden when not using vertical alignment.
foo = long_function_name(var_one, var_two,
    var_three, var_four)

# Further indentation required as indentation is not distinguishable.
def long_function_name(
    var_one, var_two, var_three,
    var_four):
    print(var_one)

Use whitespace

  • To surround these binary operators with a single space on either side: assignment (=), augmented assignment (+=, -= etc.), comparisons (==, <, >, !=, <>, <=, >=, in, not in, is, is not), Booleans (and, or, not).
  • To surround operators starting with those with lower priority. hypot2 = x*x + y*y
  • After commas except before a close parenthesis

Avoid whitespace

  • Immediately inside parentheses. spam(ham[1], {eggs: 2})
  • Before commas, semicolons, colons
  • Between function/list name and the following parentheses/brackets. (spam(1) and spam_list[1])
  • Around the = sign in keyword arguments or default values. (print(a, b, sep=''))

Line length

Recommended maximum line length is 79 symbols (and the newline symbol, making the total 80).

Why?

  • To open code in narrow editor windows. Useful in code reviews, comparisons.
  • To avoid automatic wrapping as it is harder to read than manual one.

The preferred way of wrapping long lines is by using Python’s implied line continuation inside parentheses, brackets and braces. Long lines can be broken over multiple lines by wrapping expressions in parentheses.

Backslashes may be appropriate in some cases where the preferred way specifically fails. If you have a free choice of method, use parentheses.

Naming variables and functions

The preferred way of naming variables and functions in Python is lower_case_with_underscores.

Use descriptive names. Make code self-documenting, so that anyone would be able to understand what happens right away.

In [ ]:
# Init a & b to 0
a=0
b=0
 
# Read a & b
a,b = readvalues()
 
# Add a and b, and divide by 2
c=a+b
 
#Divide c by 2
d = c / 2
 
# print c
print(c)
In [ ]:
number1 = 0
number2 = 0
 
number1, number2  = readvalues()
 
sum = number1  + number2
 
average = sum / 2
 
print(average)

Use comments to help understand reasoning behind the code.

In [ ]:
# Our code doesn't work correctly when using a usb, so we have to add this extra checks
if (special_case_usb):
    Do_something_special