Manual:Coding conventions/Python

This page describes the coding conventions for Python projects that are a part of the MediaWiki project or supporting projects.

Preamble

edit

First, remember that code standards are only guidelines and can be violated if there is a good reason.

  • Aim for readability and obviousness over strict adherence for the sake of strict adherence.
  • Code is read much more often than it is written.
  • Be consistent with existing code, but use your best judgement. If it isn't too hard to fix up the existing code, be bold.

For anything not covered in this document, please refer to Python Enhancement Proposal 0008 for the general practice. The following sections are for the most part a summary of the most commonly referred to parts of PEP8.

Python version

edit

The minimum supported version is 2.7, but in special cases it is okay to support older versions.

Python 2.7 is no longer maintained as of 2020.

If you have not already, you should change to Python 3 for local development.

Whitespace

edit

Lines should be indented with 4 spaces.

Lines at the end of files should end with a newline, just like every other line in the file.

Try to keep lines under 80 characters long, but aim for readability and obviousness over strict adherence for the sake of strict adherence. Shorter lines are just a general side effect of good idiomatic Python - short but properly scoped descriptive names, avoiding staircase code, etc. When splitting up lines, pick the most obviously unambiguous method possible for the situation.

Module structure

edit

The standard way to distribute Python modules is to create a setup.py file and leverage a library called "distribute". There are modules that will generate the structure of a base project for you, a deprecated one is paster create which is no longer maintained. A replacement is pythong.

In general module structure should look like this:

newproject
├── bin
├── distribute_setup.py
├── docs
├── newproject
│     └── __init__.py
├── setup.py
└── tests
       ├── __init__.py
       └── newproject_tests.py

Imports

edit

Within a file it's generally a good idea to organize your imports in some fashion. Typically alphabetical order is favored, but this can become unwieldy when importing a large number of libraries. To help avoid this, it's good to separate out imports in this fashion, with each block separated from the others by an empty line:

  • Standard library imports
  • Third party imports
  • Your library imports
import os
import re
import sys

import pymongo
from sqlalchemy import create_engine, exceptions

from mymodule import MyCustomException, models, views

Here are some patterns to avoid:

import sys, os # importing different modules on the same line
from sqlalchemy import * # don't import *
from .models import util # use fully qualified names instead of relative imports
Imports expanded example
edit

Here is a more detailed abstracted version (the comments are just for explanation purposes):

# Full import of stdlib modules, in alphabetic order
import a_stdlib_module
import b_stdlib_module

# Submodules imports of stdlib modules, in alphabetic order both vertically and horizontally
from another_stdlib_module import a_stdlib_submodule, b_stdlib_submodule
from c_stdlib_module import another_stdlib_submodule, last_stdlib_submodule

# Full import of 3rd party modules, in alphabetic order
import a_third_party_module
import b_third_party_module

# Submodules imports of 3rd party modules, in alphabetic order both vertically and horizontally
from another_third_party_module import a_third_submodule, b_third_submodule
from c_third_party_module import another_third_submodule, last_third_submodule

# Full import of current application modules, in alphabetic order and with absolute imports
import myapp.a_module
import myapp.b_module

# Submodules imports of current application modules, in alphabetic order both vertically and horizontally
from my_app.another_module import a_submodule, b_submodule
from my_app.c_module import another_submodule, last_submodule

Docstrings and function annotation

edit

Generally all but the simplest functions should have docstrings. These are standardized in PEP 257

def fractionize(first, second=1):
    """
    Make a string representation of a fraction of two numbers.

    Keyword arguments:
    first -- the top of the fraction
    second -- the bottom of the fraction (anything but 0)
    """
    return "{0} / {1}" % (first, second)

This makes it possible to automatically generate docs, as well as use Python's built-in help function.

In Python 3.3 and above PEP 3107 specifies syntax for function annotations.

Function annotations do not have a completely set use case, but a common emerging case is for improved help docs and for type annotation.

def parse(source: "the original document",
          lang: "what markup syntax is being used? [md|rst|textile]",
          force: "Ignore syntax errors?"):

Naming conflicts

edit

Conflicting with builtins is a somewhat common problem. There are some builtin names (like hash and id) that you may want to use in your code. The PEP8 way to deal with these conflicts is by appending an underscore to the name, such as hash_ or class_ (although if you're naming a variable class_ that may be a code smell).

If you find yourself in conflict with the name of some part of another module, import as is your friend.

from sqlalchemy import exceptions as sa_exceptions
from mymodule import exceptions as my_exceptions

See also

edit