-
Notifications
You must be signed in to change notification settings - Fork 0
Python Style Guide
This document is a diff - it describes how Numenta's Python guidelines differ from or are unspecified by PEP-8 and PEP-257.
It's meant to be succinct. Generally, we wish to remain close to the best practices of the Python community.
As of March 2012, Numenta software requires Python 2.6.7.
Pylint should be set up and configured after following the [Basic Developer Setup]. This puts the "pylint" command on your path with a symbolic link at ~/.pylintrc to the Numenta configuration. The pylint command enforces many of the style guidelines from this document and PEP8.
If your source file is meant to be an executable, then:
make the first line a shebang:
#!/usr/bin/env python
and change its mode:
$ chmod +x
At the top of each source file (after the shebang, if any), include the following legal notice:
# ----------------------------------------------------------------------
# Numenta Platform for Intelligent Computing (NuPIC)
# Copyright (C) 2013, Numenta, Inc. Unless you have purchased from
# Numenta, Inc. a separate commercial license for this software code, the
# following terms and conditions apply:
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see http://www.gnu.org/licenses.
#
# http://numenta.org/licenses/
# ----------------------------------------------------------------------
In the date, include any years in which the code was modified, e.g. "2006-2009", "2006, 2008, 2010".
After the legal notice, include a docstring to describe the contents of the file. If the file comprises a single class definition, then make it minimalist -- put your more-detailed documentation in the class's docstring.
Do not include tests in source files. All tests should be in the tests directory at the top level of the repository.
Use two (2) spaces per indentation level.
Separate top level functions and classes by three blank lines. Separate methods inside classes by two blank lines. Do not use more than one consecutive blank line in a code block. Files should end with a single newline character (most editors will show this as a blank line but some, such as Vim, will not display it).
For Numenta-internal code, do not wrap import statements in try-except blocks and attempt to recover dynamically. (If there's an exception, consider that a configuration bug.)
No:
try:
import thirdpartymodule
except ImportError:
# Attempt brave recovery
Yes:
import thirdpartymodule
For code meant to be used externally, brave recoveries are acceptable.
Multiple imports are okay in a from statement.
Yes:
from subprocess import Popen, PIPE
If there are many, enclose them in parentheses to allow multiple lines.
Yes:
from my_fancy_module import (myFunc1,
myFunc2,
myVeryLongFunc3)
Do not use import *. It pollutes the global namespace and makes it difficult to determine where symbols are defined.
No:
from subprocess import *
By default, double quotes should be used for all strings.
Do not leave trailing whitespace on non-blank lines.
For loosely-binding arithmetic operators (+, -), use surrounding spaces.
For tightly-binding ones (*, /, and **), you may elide the spaces.
No:
i=i+1
submitted +=1
c = (a+b) * (a-b)
Yes:
i = i + 1
submitted += 1
x = x*2 - 1
hypot2 = x*x + y*y
c = (a + b) * (a - b)
Do not use compound statements.
No:
if foo: print foo
Yes:
if foo:
print foo
No:
x = foo; print x
Yes:
x = foo
print x
Numenta's guidelines for where to use docstrings are the same as in PEP-257.
Numenta's guidelines for content and format of docstrings is evolving. Watch this space.
- format: Sphinx and/or reStructuredText?
- parameters, return values?
- first line: summary. no word wrap. blank line.
Comments are for describing why, not what. Favor structure over commentary.
If your comment says "the next n lines do y," then remove it and make those next n lines a new (unit-testable) function.
No:
#-------------------------------
# first, flurblize the glambuz
... code ...
#-------------------------------
# then, deblurflize the buzglam
... even more code ...
Yes:
def flurblize(glambuz):
... code ...
def deblurflize(buzglam):
... code ...
flurblize(glambuz)
deblurflize(buzglam)
Separate commented sections with a blank line.
No:
# This comment applies to the next two lines.
foo()
bar()
# This is another comment.
baz()
Yes:
# This comment applies to the next two lines.
foo()
bar()
# This is another comment.
baz()
Do not use a comments on the same line as a statement/expession. It's hard to read.
No:
x = x + 1 # Compensate for border
Yes:
# Compensate for border.
x = x + 1
Put comments which describe the predicate inside the block itself.
No:
# User selected "Other"
if radioIndex == len(self.intervals):
self.intervalOther.SetFocus()
# User selected one of the pre-defined intervals
else:
self.interval = int(self.intervals[radioIndex])
Yes:
if radioIndex == len(self.intervals):
# User selected "Other"
self.intervalOther.SetFocus()
else:
# User selected one of the pre-defined intervals
self.interval = int(self.intervals[radioIndex])
In contrast, put comments which describe the context for the entire if/else section before the if.
Make directories (packages) and files (modules) snake_cased -- i.e. lowercase letters with an underscore for separating words. ASCII only. Shorter is better.
No:
# directory layout
production/
ProductionWorker.py
productionJobParamsSchema.json
# code
from nupic.cluster.production.ProductionWorker import ProductionWorker
Yes:
# directory layout
production/
production_worker.py
production_job_params_schema.json
# code
from nupic.cluster.production.production_worker import ProductionWorker
Include the suffix ".py" on all Python modules. Exception: ".pyw" for modules to be launched from a graphical shell.
Give functions, methods, and instance variables names in camelCase.
For non-public attributes, add a leading underscore. Whether just one ('_') or two ('__') is an interesting question.
Using just one ('_') has two benefits:
- it's the most wide-spread convention
- unit tests can easily access those methods
On the other hand, using two ('__') is useful for inheritance. Names with this prefix (but without that suffix) evoke name mangling, making them appropriately difficult to access from outside the class. So attributes meant to be truly private (and not merely protected) will be immune to accidental overwriting by subclasses.
You should not access mangled attribute names outside the class.
Give module-level "constants" SHOUT_CASE names. If non-public, include a leading underscore.
Give module-level (actual) variables the prefix "g_", to indicate "global". (These should never be public, so there's no need to include a leading underscore to distinguish public from non-public.)
"Dummy" variables are ones required but unused. For example, you may need to supply a callback not all of whose arguments you need. Or you call a function that returns multiple values, but you don't use them all.
Give dummy variables a leading underscore.
Yes:
(hostname, _port) = getHostnameAndPort()
# _port is unused
print hostname
Or, use just an underscore.
Yes:
(hostname, _) = getHostnameAndPort()
# _ is unused
print hostname
Sometimes we use code with naming conventions that differ from Numenta's. For example, wxPython requires one to use its naming conventions (to subclass its classes and provide event handlers).
In this case, hide these convention conflicts in a helper class. The helper should encapsulate the use of the third-party code, do the heavy lifting, and expose only Numenta-style names.
Make your units small. Design for testability.
DRY stands for "don't repeat yourself". Do not copy-and-paste.
Rather than copy-and-paste, make a new, parametrized function.
Use only new-style classes -- those which subclass object, directly or indirectly.
No:
class Foo:
...
Yes:
class Foo(object):
...
Initialize all members in init, even if simply to None. This serves to declare what all instance variables are in use. Do not create attributes outside of init.
No:
def __init__(self):
pass
def bar(self, foo):
self._foo = foo
Yes:
def __init__(self):
self._foo = None
def bar(self, foo):
self._foo = foo
Do not use properties unless necessary. If you must:
- Declare them immediately after init.
- Document why, right before the definition.
Yes:
# coincidenceCount is a property because it's:
# - computed and
# - read-only
@property
coincidenceCount(_getCoincidenceCount, None,
None, "Number of coincidences")
def _getCoincidenceCount(self):
return len(self.coincidences)
Use list comprehensions instead of map, filter, and reduce. In Python, list comprehensions are highly optimized. HOFs are deprecated.