Warning
This document is currently in Draft form and is subject to change.
This section describes requirements and guidelines that affiliated packages will have to follow before being considered for integration as a module in the core package.
Note
There are multiple options for testing PEP8 compliance of code, see Testing Guidelines (Draft) for more information.
See Emacs setup for following coding guidelines for some configuration options for Emacs that helps in ensuring conformance to PEP8.
Astropy source code should contain a comment at the beginning of the file (or imppediately after the #!/usr/bin env python command, if relevant) pointing to the license for the Astropy source code. This line should say:
# Licensed under a 3-clause BSD style license - see LICENSE.rst
The import numpy as np, import matplotlib as mpl, and import matplotlib.pyplot as plt naming conventions should be used wherever relevant. from packagename import * should never be used, except as a tool to flatten the namespace of a module. An example of the allowed usage is given in Acceptable use of from module import *.
Classes should either use direct variable access, or python’s property mechanism for setting object instance variables. get_value/set_value style methods should be used only when getting and setting the values requires a computationally-expensive operation. Properties vs. get_/set_ below illustrates this guideline.
All new classes should be new-style classes inheriting from object (in Python 3 this is a non-issue as all classes are new-style by default). The one exception to this rule is older classes in third-party libraries such the Python standard library or numpy.
Classes should use the builtin super() function when making calls to methods in their super-class(es) unless there are specific reasons not to. super() should be used consistently in all subclasses since it does not work otherwise. super() vs. Direct Calling illustrates why this is important.
Multiple inheritance should be avoided in general without good reason. Mulitple inheritance is complicated to implement well, which is why many object-oriented languages, like Java, do not allow it at all. Python does enable multiple inheritance through use of the C3 Linearization algorithm, which provides a consistent method resolution ordering. Non-trivial multiple-inheritance schemes should not be attempted without good justification, or without understanding how C3 is used to determine method resolution order. However, trivial multiple inheritance using orthogonal base classes, known as the ‘mixin’ pattern, may be used.
__init__.py files for modules should not contain any significant implementation code. __init__.py can contain docstrings and code for organizing the module layout, however (e.g. from submodule import * in accord with the guideline above). If a module is small enough that it fits in one file, it should simple be a single file, rather than a directory with an __init__.py file.
When try...except blocks are used to catch exceptions, the as syntax should always be used, because this is available in all supported versions of python and is less ambiguous syntax (see try...except block “as” syntax).
Affiliated packages are required to follow the layout and documentation form of the template package included in the core package source distribution.
Command-line scripts should follow the form outlined in the Writing Command-Line Scripts document.
This section shows a few examples (not all of which are correct!) to illustrate points from the guidelines. These will be moved into the template project once it has been written.
This example shows a sample class illustrating the guideline regarding the use of properties as opposed to getter/setter methods.
Let’s assuming you’ve defined a Star class and create an instance like this:
>>> s = Star(B=5.48, V=4.83)
You should always use attribute syntax like this:
>>> s.color = 0.4
>>> print s.color
0.4
Rather than like this:
>>> s.set_color(0.4) #Bad form!
>>> print s.get_color() #Bad form!
0.4
Using python properties, attribute syntax can still do anything possible with a get/set method. For lengthy or complex calculations, however, use a method:
>>> print s.compute_color(5800, age=5e9)
0.4
This example shows why the use of super() leads to a more consistent method resolution order than manually calling methods of the super classes in a multiple inheritance case:
# This is dangerous and bug-prone!
class A(object):
def method(self):
print 'Doing A'
class B(A):
def method(self):
print 'Doing B'
A.method(self)
class C(A):
def method(self):
print 'Doing C'
A.method(self)
class D(C, B):
def method(self):
print 'Doing D'
C.method(self)
B.method(self)
if you then do:
>>> b = B()
>>> b.method()
you will see:
Doing B
Doing A
which is what you expect, and similarly for C. However, if you do:
>>> d = D()
>>> d.method()
you might expect to see the methods called in the order D, B, C, A but instead you see:
Doing D
Doing C
Doing A
Doing B
Doing A
because both B.method() and C.method() call A.method() unaware of the fact that they’re being called as part of a chain in a hierarchy. When C.method() is called it is unaware that it’s being called from a subclass that inherts from both B and C, and that B.method() should be called next. By calling super() the entire method resolution order for D is precomputed, enabling each superclass to cooperatively determine which class should be handed control in the next super() call:
# This is safer
class A(object):
def method(self):
print 'Doing A'
class B(A):
def method(self):
print 'Doing B'
super(B, self).method()
class C(A):
def method(self):
print 'Doing C'
super(C, self).method()
class D(C, B):
def method(self):
print 'Doing D'
super(D, self).method()
>>> d = D()
>>> d.method()
Doing D
Doing C
Doing B
Doing A
As you can see, each superclass’s method is entered only once. For this to work it is very important that each method in a class that calls its superclass’s version of that method use super() instead of calling the method directly. In the most common case of single-inheritance, using super() is functionally equivalent to calling the superclass’s method directly. But as soon as a class is used in a multiple-inheritance hierarchy it must use super() in order to cooperate with other classes in the hierarchy.
Note
For more info on the pros and cons of using super, see http://rhettinger.wordpress.com/2011/05/26/super-considered-super/ or http://keithdevens.com/weblog/archive/2011/Mar/16/Python.super)
from module import * is discouraged in a module that contains implementation code, as it impedes clarity and often imports unused variables. It can, however, be used for a package that is laid out in the following manner:
packagename
packagename/__init__.py
packagename/submodule1.py
packagename/submodule2.py
In this case, packagename/__init__.py may be:
"""
A docstring describing the package goes here
"""
from submodule1 import *
from submodule2 import *
This allows functions or classes in the submodules to be used directly as packagename.foo rather than packagename.submodule1.foo. If this is used, it is strongly recommended that the submodules make use of the __all__ variable to specify which modules should be imported. Thus, submodule2.py might read:
from numpy import array,linspace
__all__ = ('foo','AClass')
def foo(bar):
#the function would be defined here
pass
class AClass(object):
#the class is defined here
pass
This ensures that from submodule import * only imports foo() and AClass, but not numpy.array or numpy.linspace().
Catching of exceptions should always use this syntax:
try:
... some code that might produce a variety of exceptions ...
except ImportError as e:
if 'somemodule' in e.args[0]"
#for whatever reason, failed import of somemodule is ok
pass
else:
raise
except ValueError, TypeError as e:
msg = 'Hit an input problem, which is ok,'
msg2 = 'but we're printing it here just so you know:'
print msg, msg2, e
This avoids the old style syntax of except ImportError, e or except (ValueError,TypeError), e, which is dangerous because it’s easy to instead accidentally do something like except ValueError,TypeError, which won’t catch TypeError.
Further tips and hints relating to the coding guidelines are included below.