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.
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 , xGood:
if x == 4: print x, y; x, y = y, xImmediately 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.
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.