Skip to content
Scott Purdy edited this page Jun 20, 2013 · 1 revision

Introduction

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.

Python Version

As of March 2012, Numenta software requires Python 2.6.7.

Pylint

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.

File Layout

Executables

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

Legal Notice

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

Module Docstring

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.

Tests

Do not include tests in source files. All tests should be in the tests directory at the top level of the repository.

Coding Style

Indentation

Use two (2) spaces per indentation level.

Blank Lines

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

Imports

try/except

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.

from statements

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)

import *

Do not use import *. It pollutes the global namespace and makes it difficult to determine where symbols are defined.

No:

from subprocess import *

Quotes

By default, double quotes should be used for all strings.

Spaces

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)

Compound Statements

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 

Docstrings

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

Purpose of Comments

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)

Commented Sections

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()

Inline Comments

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

Comments in if/else blocks

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.

Naming Conventions

Directory and File Names

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.

Function, Method, and Instance Variable Names

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.

Module-global Variables

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 Names

"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

Interfacing with third party code

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.

Programming Guidelines

Testable Code

Make your units small. Design for testability.

Stay DRY

DRY stands for "don't repeat yourself". Do not copy-and-paste.

Rather than copy-and-paste, make a new, parametrized function.

Classes

Use only new-style classes -- those which subclass object, directly or indirectly.

No:

class Foo:
  ...

Yes:

class Foo(object):
  ...

Data Member Initialization

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

Properties

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)

List Comprehensions

Use list comprehensions instead of map, filter, and reduce. In Python, list comprehensions are highly optimized. HOFs are deprecated.

Clone this wiki locally