Python Coding Style Guidelines

Documented here for the benefit of subcontractors and anyone curious.

Most rules from PEP-8 apply, except:

The rules from Other Recommendations section of PEP-8 are mandatory. Don’t ever use semicolons or one-line if statements. On a related note, use if expressions only if they fit on one line.


General rules

Many of these rules only apply when in doubt. It’s hard to read source code if there’s a different style for every fraction of a module, but having just 75% of code conform to the same rules makes it so much more pleasant to navigate. 100% compliance to the rules is not required and is simply unnecessary. Still, having a habit of writing certain constructs in the same way saves time even when writing it, and a lot more when reading.

Also, whenever you can edit your source code not to require any comments — that’s much better than the best of comments or docstrings. Consider this:

# check if user is allowed to edit
if (user.logged_in and 'edit' in Role.get(user).permissions):
    ...

and this:

may_edit = user.logged_in and 'edit' in Role.get(user).permissions
if may_edit:
    ...

The latter is much better, because it’s easier to verify being correct, because it can be considered as two separate claims:

  1. that we decide if the user can edit based on criteria on line 1
  2. that the code in that if suite should only be executed if user may edit

This approach is much better than writing “clever” code that needs comments to explain what is it doing. Let the code explain itself.

That’s much easier to accomplish when you adopt some more high-level coding style, that is if you name classes and names in such a way it’s easy to understand the API just by reading the names.

Writing your classes and packages to behave in similar ways helps save time on reading (and writing!) docs, debugging and adding features. This means that your code will be a little bit more boring, but I think we all would rather avoid the excitement of figuring something out, if that could have been made trivial in the first place. Please do repeat yourself whenever you did something well. Also repeat others as that’s how we learn. “Don’t repeat yourself” (aka DRY) only applies if what you tend to do is better avoided. You know, it’s similar to how one will never get any good in cooking if he never cooks the same thing twice.

Code reuse is a good thing, but lets not forget that when programming we repeat ourselves in some ways all the time, and intoducing a generic solution will quite often increase the amount of code one needs to write to do the same thing as before, and even if not, one should consider the time spent learning this new system and how proficient one would be with it compared to a simpler or more narrowly aimed implementation. One might think that some framework saves him from repeating himself again and again, but at the same time one repeats the interaction with that framework again and again anyway, so it’s a choice between two cases of repetition and should be considered as such.

Apparently people get bored writing defs and ifs all the time, but a couple of ifs is usually better that a dict of undecipherable regular expressions plus a 50Kb dependency.

Anyway, writing consitently good code is what matters in the end and a coding style is a good start.


String quoting

For example:

getattr(ob, 'attribute')
struct.unpack('iii', data)
print "Hello world"
log.warning("Could not connect to %s", host)

This might seem unnecessary but in fact it helps grasp semantics of each string faster and you can adjust source code highlighting to further help you with that. It’s a very simple rule but more effective than it might seem at first.


Variable names

The following also applies to attribute, function and method names.

Also, when using composite variable names there is an issue of word order. For example an attribute holding an item class can be named item_cls or cls_item. If the name is one of a kind, natural word order should be preserved, but if there are a few related names sharing one of the words, then that word should always be placed first. So if there’s an OK button and a Cancel button, then they should be stored in btn_ok and btn_cancel attributes.


Abbreviations

Having a system of consistent abbreviations to use in variable names helps a lot when reading the code later and saves time when first naming them.


Decorators


Magic method names

You should not invent __smth__ and similar just as PEP-8 says, but there’s an exception:


Class names


Imports


Comments formatting


Docstrings

For example:

def nop(*args, **kw):
    """
        Take arguments and do nothing. For example:

            nop(nop, nop())

    """
    pass

Just like usual block indentation it helps discern logical blocks and makes reading source code much more pleaseant and fast.


Long expressions and argument lists

Example:

print "[[[ %s-%s ]]]" % \
    (var1, var2) # BAD -- newline escape

print ("[[[  %s-%s ]]]" %
    (var1, var2)) # BAD -- closing bracket in the wrong place

print ("[[[  %s-%s ]]]" %
    (var1, var2) # BAD -- the operator is on the wrong line
)

print ("[[[  %s-%s ]]]"
    % (var1, var2) # GOOD
)

The only reason that looks silly is that there was no real need for line break in the first place. Look for real examples below.

Another example, which also shows why the last bracket rule matters:

# BAD
def func(self, a, b, c, d,
         e, f, g):
    sum = a + b + c + d
    sum += e + f + g
    return sum

# GOOD
def func(self,
    a, b, c, d,
    e, f, g
):
    sum = a + b + c + d
    sum += e + f + g
    return sum

# GOOD (note the added docstring)
def func(self,
    a, b, c, d,
    e, f, g
):
    """
        Return a sum of unnecessarily named arguments
    """
    sum = a + b + c + d
    sum += e + f + g
    return sum

Examples from real code

__all__ = [
    'STMHistory', 'Controller', 'LocalController', 'LockingController',
    'AbstractSubject', 'AbstractListener', 'Link',
    'CircularityError',
]


make.attrs(
    # note the spaces around the '=' sign, if argument passing takes
    # a lot of lines those spaces make it more readable
    items = lambda self: [
            self.file,
            self.tools,
            self.help,
        ], # note indentation of this closing bracket -- lambda added one level
    file = lambda self:
        Menu("&File", [
            # note the double quotes for menu item text
            # and single quotes for icon spec and key
            Menu("Upload files",
                Icon('nav_upload_files'),
                key='upload_files'
            ),
            Menu("Upload folder",
                Icon('nav_upload_folder'),
                key='upload_folder'
            ),
        ]),
    #...
)


# next example is borderline -- it's kept as one construct only for
# demonstration purposes
@event('menu', 'copy_url')
def on_menu_copy_url(self):
    WxClipboard.data = wx.URLDataObject('\n'.join(
        map(self.generate_url,
            filter(lambda item: item.is_file,
                self.selected_objs()
            )
        )
    ))


# note how ending bracket on its own line makes the code so much more readable
if not (
    (0 <= red <= 1)
    and (0 <= green <= 1)
    and (0 <= blue <= 1)
):
    raise ValueError("Color component out of range")


# example of more than one keyword argument per line
create_frame(
    allow_resize=True, allow_maximize=True,
    posx=50, posy=100
)

Exception to the “no newline escapes” rule

def prepare_ctx_menu(self, menu):
    items = ...
    files = ...
    menu.copy_url.enabled = bool(files)
    menu.download.enabled = \
        menu.delete.enabled = \
        menu.properties.enabled = bool(items)
    #menu. ...
Feel free to read more, email me or subscribe to the RSS.