Coding Style

Our coding style is kind of a hodgepodge between different standard Python styles. It’s not a 100% Pythonic in a few places, but those differences are simply a matter of opinion anway. All of these “rules” are technically completely optional, but they each have different levels of importance. If you feel an alternative solution would be better in a particular situation, go ahead with it. We can always change it later, if needed. But consider these guidelines the default if you don’t know what to do.

Overall structure

Try to keep the code as functionally-oriented as possible. Keep as little as state as possible in objects, and use objects themselves sparingly. Instead, use class as a way to make data structures, purely as a tool for storage, and make functions in the same file that operate on that structure. However, don’t use procedural code. Essentially, treat everything like a math equation, and use higher order functions (using lambdas and such) to reduce state as much as possible. For example,

from random import random

def move_cars(car_positions):
    return map(lambda x: x + 1 if random() > 0.3 else x,
               car_positions)

def output_car(car_position):
    return '-' * car_position

def run_step_of_race(state):
    return {'time': state['time'] - 1,
            'car_positions': move_cars(state['car_positions'])}

def draw(state):
    print ''
    print '\n'.join(map(output_car, state['car_positions']))

def race(state):
    draw(state)
    if state['time']:
        race(run_step_of_race(state))

race({'time': 5,
      'car_positions': [1, 1, 1]})

is preffered, rather than making classes for Car and Race.

Note

This is just a stub example, intended to give the general feel of functional code. Judgment calls are needed, but default to functional.

For more, see this article.

Specific guidelines

These are some guidelines listed in no real order.

Indentation

Use four spaces for indentation.

Bad:

def function(x):
  return 2

Good:

def function(x):
    return 2

Don’t make indentation greater than 3 levels deep. Split deep indentation into functions instead.

Bad:

def function(x):
    if x == 3:
        for n in range(x, 5):
            if n == (x % 2):
                for k in range(2, 5):
                    print 'hello'

Good:

def function(x):
    if x == 3:
        for n in range(x, 5):
            do_smth(n, x)

def do_smth(n, x):
    if n == (x % 2):
        for k in range(2, 5):
            print 'hello'

Align long function arguments with each other.

Bad:

foo = long_function_name(long_name_long_name_long_name, long_name_long_name_long_name,
long_name_long_name_long_name, long_name_long_name_long_name)

Good:

foo = long_function_name(long_name_long_name_long_name, long_name_long_name_long_name,
                         long_name_long_name_long_name, long_name_long_name_long_name)

Statements

Use only one statement per line.

Bad:

print 'one'; print 'two'

if x == 1: print 'one'

if <complex comparison> and <other complex comparison>:
    # do something

Good:

print 'one'
print 'two'

if x == 1:
    print 'one'

cond1 = <complex comparison>
cond2 = <other complex comparison>
if cond1 and cond2:
    # do something

Don’t make a line longer than 80 characters.

Surround top-level functions and classes with two blank lines.

Bad:

class Something(Object):
  def __init__(self, yay):
    # ...
  def wow(do_smth):
    # ...
def something_else(two, three):
  # ...

Good:

class Something(Object):
    def __init__(self, yay):
      # ...
    def wow(do_smth):
      # ...


def something_else(two, three):
    # ...

Returning values

Don’t return None, raise an Exception instead.

Bad:

def function(x):
    if x % 2:
        return None
    else:
        # do computation...

Good:

class ArgumentError(Exception): pass


def function(x):
    if x % 2:
        raise ArgumentError
    else:
        # do computation...

Prefer keeping a single exit point to the function, but don’t completely avoid multiple exit points if it makes the code clearer.

Imports

Imports should be on separate lines.

Bad:

import os, sys

Good:

import os
import sys

Unpack

Use unpacking wherever possible. For example, remembering that enumerate gives you a tuple of the index and the item:

Bad:

for item in enumerate(some_list):
  # use item[0] for index and item[1] for actual item

Good:

for index, item in enumerate(some_list):
  # use index and item

If you need to assign something while unpacking but don’t actually need the variable, you can use __ instead (two underscores).

Note

You can use a single underscore (_) as well, but this happens to interfere with an alias of the gettext function, so to avoid any mishaps, use __.

Whitespace

Avoid exteranous whitespace in the following situations:

  • Immediately inside parentheses, brackets or braces.

    Bad:

    spam( ham[ 1 ], { eggs: 2 } )
    

    Good:

    spam(ham[1], {egss: 2})
    
  • Immediately before a comma, semicolon, or colon.

    Bad:

    if x == 4 : print x , y ; x , y = y , x
    

    Good:

    if x == 4: print x, y; x, y = y, x
    
  • Immediately before the open parenthesis that starts the argument list of a function call.

    Bad:

    spam (1)
    

    Good:

    spam(1)
    

Avoid having more than one space around an operator to align it with another statement.

Bad:

x                 = 1
y                 = 2
some_long_varible = 3

Good:

x = 1
y = 2
some_long_varible = 3

But, surround binary opertors (=, <=, >=, ==, in, not in, and, or, etc.) with a single space on either side, in all cases.

Inline Comments

Use inline comments sparingly, only when they’re useful.

Bad:

x = x + 1 # increment x

Good:

x = x + 1 # Compensate for border

Names

Use snake_case for everything except class names, which should be in CamelCase.

Bad:

def SomeFunctionName(argument): pass

class long_name_thing(Object): pass

Good:

def some_function_name(argument): pass

class LongNameThing(Object): pass

When using abbreviations in any name, capitalize all the letters of it.

Bad:

def get_http_things(url): pass

class HttpResponseWhatever(Object): pass

Good:

def get_HTTP_things(URL): pass

class HTTPResponseWhatever(Object): pass

DO NOT use l or O for single-letter variable names. They can be easily confused in most common fonts for 1 and 0. Use single letter variable names themselves sparingly, only for loop indexes. If there’s very deeply nested loops, use more descriptive names for the indexes.

Use all capitals for constant names.

Strings

For literal strings, always use double quotes for anything longer than about 3-4 characters.

Bad:

'long_string'
"ab"
'\n\n\n'

Good:

"long_string"
'ab'
"\n\n\n"

Operators

Use the is not operator instead of not ... is.

Bad:

if foo is not None: ...

Good:

if not foo is None: ...

For short, higher-order functions defined inside of other functions, prefer to use the lamda operator instead of def.

Bad:

def function(x):
  def f(x): return 2 * x
  # using f

Good:

def function(x):
  f = lambda x: 2 * x

Functional Programming

Prefer to use map, filter, and other functions, along with the functools module instead of things like list comprehensions and loops.

Note

List comprehensions are Pythonic, so judgment calls need to be made a lot here. But the convention in this project is to use map and filter and others.

Bad:

squares = [x * x for x in range(10)]

Good:

squares = map(lambda x: x * x, range(10))

Bad:

someList = [1, 2, 3, 4, 5]
listWithTwoAdded = map(lambda x: x + 2, someList)

Good:

import functools as ft

someList = [1, 2, 3, 4, 5]
add = lambda x, y: x + y
listWithTwoAdded = map(ft.partial(add, 2), someList)

Note

In this particular case, the lambda alone was clearer. But using functools, especially functools.partial to create abstractions using functions is incredibly useful when programming functionally.

Prefer pure functions that rely on no outside state.

Bad:

some_list = []

def foo(bar):
    some_list.append(bar)

foo('baz')

Good:

def foo(bar, lst):
  return lst + [bar]

some_list = []
now_list = foo('baz', some_list)

Prefer recursion to loops.

Note

In cases where performance would be affected, this does not apply.

Bad:

def rule_sequence(s, rules):
  for rule in rules:
      s = rule(s)
      if s == None:
          break

  return s

Good:

def rule_sequence(s, rules):
  if s == None or not rules:
      return s
  else:
      return rule_sequence(rules[0](s), rules[1:])

The Fn Library

The Fn Library contains a lot of useful add-ons for functional programming in Python.

Use pipelines using function composition instead of a sequence of steps. Use fn.monad.Option to make error handling easier in these cases.

Bad:

class Request(dict):
    def parameter(self, name):
        return self.get(name, None)

r = Request(testing="Fixed", empty="   ")
param = r.parameter("testing")
if param is None:
    fixed = ""
else:
    param = param.strip()
    if len(param) == 0:
        fixed = ""
    else:
        fixed = param.upper()

Good:

from operator import methodcaller
from fn.monad import optionable

class Request(dict):
    @optionable
    def parameter(self, name):
        return self.get(name, None)

r = Request(testing="Fixed", empty="   ")
fixed = r.parameter("testing")
         .map(methodcaller("strip"))
         .filter(len)
         .map(methodcaller("upper"))
         .get_or("")

If the code’s simpler, use fn._ to define small lambdas.

Bad:

map(lambda x: x * 2, [1, 2, 3, 4, 5])

Good:

from fn import _
map(_ * 2, [1, 2, 3, 4, 5])

There are other things that fn provides, check the github repo’s README for more.

Docstrings

Docstring guidelines are covered by the standard PEP 257.

Exceptions

Always mention a specific exception when using the except operator.

Bad:

try:
  import some_lib
except:
  # recover from some_lib ImportError

Good:

try:
  import some_lib
except ImportError:
  # recover from some_lib ImportError

To avoid masking bugs, use the absolute minimum amount of code necessary in the try block, to avoid having except clauses that catch too many errors.

Bad:

try:
  # Too broad!
  return handle_value(collection[key])
except KeyError:
  # Will also catch KeyError raised by handle_value()
  return key_not_found(key)

Good:

try:
  value = collection[key]
except KeyError:
  return key_not_found(key)
else:
  return handle_value(value)

Threading

Do not rely on the atomicity of built-in types.

While Python’s built-in data types such as dictionaries appear to have atomic operations, there are corner cases where they aren’t atomic (e.g. if __hash__ or __eq__ are implemented as Python methods) and their atomicity should not be relied upon. Neither should you rely on atomic variable assignment (since this in turn depends on dictionaries).

Use the Queue module’s Queue data type as the preferred way to communicate data between threads. Otherwise, use the threading module and its locking primitives. Learn about the proper use of condition variables so you can use threading.Condition instead of using lower-level locks.


For anything that isn’t mentioned in these guidelines, refer to PEP 8.