1.2. Python

1.2.1. General

import this
  • For python, we have scame that checks for PEP8 and some other things.
  • Python PEP 8 Style guide for Python code.
  • Favour single inheritance.
  • As the second best option, use Mixins to reuse code and avoid multiple inheritance.
  • Docstring always uses multiline strings with double quotes and empty first and last lines. There’s no blank line either before or after the docstring.
def some_function(argument):
    """
    Single line docstring.
    """
    code_starts_here()


class SomeClass(ParentClass):
    """
    Quick explanation of the role of this class which is a bit long so we
    wrap it.

    More details to follow in long paragraphs.
    """

    def method_starts_here(self):
      """
      Some description.
      """
  • Class members or instance members can be documented inline using the following syntax:
class SomeClass(ParentClass):
    """
    Docstring for class.
    """

    #: Docstring for class member.
    #: Can be on multiple lines.
    class_member = True
  • All test functions, test methods and test suites will have docstrings.
  • Tests methods will be prefixed with test_.
  • Tests suites (test class) will be prefixed with Test.
  • Test’s docstring should state what is the expected behavior based on test’s input.
def test_methodName(self):
    """
    When doing this `methodName` will raise an AssertionError.
    """
  • When adding linter exception, always add a comment explaining the reason why the exception was added.
  • Use named parameters for calling methods. This will reduce future refactoring efforts.
  • If a method or class initialization / constructor method has more than 1 argument, always use named parameters for calling that method.
  • Try to use single quotes for strings. This will make it easier to generate quoted text for UI or HTML.

YES

other_var = 'string'
some_var = 'string "b" yes'

NO

other_bad = "string"
some_bad = "string 'b' yes"
  • As we look forward to support Python 3, we should use from __future__ import unicode_literals, as it helps with readability, instead of explicitly having to mark every text string as u’unicode’. More details here: http://python-future.org/compatible_idioms.html#strings-and-bytes
  • All byte strings should be explicitly marked as in b’byte string’.
  • As PEP8 recommends, don’t use ‘’ to split long lines. Wrap long lines by using Python’s implied line continuation inside parentheses, brackets and braces. More details here: http://www.python.org/dev/peps/pep-0008/#maximum-line-length
  • Multi-line splits using parentheses, brackets, etc, will follow the normal indentation rules. The code might look ugly and then exceptions are allowed.
  • Define all class members at the beginning of class definition. Don’t interleave methods and class members definitions. This should make it easy to identify all class members used by the class.
  • Define all instance members inside the __init__() method. This should make it easy to identity all instance members used by the class and reduce the risk of using the same member for more than one purpose.
  • Decode all input to Unicode and encode all output from Unicode. Do all internal text handling in Unicode.
input_raw_string = read_from_wire()
input_string = input_raw_string.decode('utf-8')

# Only work with Unicode data.
output_string = process_something(input_string)

output_raw_string = output_string.encode('utf-8')
send_to_wire(output_raw_string)
  • UTF-8 is not Unicode. Unicode is a character set and UTF-8 is a particular way of encoding Unicode.
  • When a method does not use the self attribute, this is a code smell that this method should be placed somewhere else.

1.2.2. Abstract

Abstract classes are classes that contain one or more abstract methods.

An abstract method is a method that is declared, but contains no implementation. Calling an abstract method will raise NotImplementedError.

Abstract classes may not be instantiated. They require subclasses to provide implementations for the abstract methods.

Abstract classes are somehow similar to interfaces, but they also come with partial implementations.

Abstract methods can be public or private.

When raising the NotImplementedError, make sure you add some text to uniquely identify this abstract methods. During development, we might report errors without a traceback, and having a custom message associated with the error makes it easier to identify what is wrong.

When defining an abstract class, suffix it with Abstract name. This should let others know that this class should not be directly instantiated.

All abstract methods should be defined at the beginning of the class, even before the __init__ method or class variables. They should be documented with docstring, describing their usage and how they should be implemented.

class LocationAbstract(LocationBase):
    """
    Shared code by some type of locations.

    More information about the functionality provided by this class.
    """

    def getProtocol(self):
        """
        Return the client protocol used to connect to the FTP server.
        """
        raise NotImplementedError('_getProtocol not implemented.')

    def _beforeConnection(self, client):
        """
        Called before the FTP command channel connection is initiated.

        Should return a deferred.
        """
        raise NotImplementedError('_beforeConnection not implemented.')

    # Class members defined by the abstract.
    # They can be public or private.
    _reactor = reactor
    REQUIRE_RESTART = ('address', 'port')

    def __init__(self, parent, configuration):
        """
        Abstract classes can implement the init method.
        """
        super(LocationAbstract, self).__init__(parent, configuration)
        # Low level FTP transport protocol.
        self._protocol = None
        self._features = ()

    def onConnect(self):
        """
        They can also fully implement some public methods.
        """
        return self._doMoreWorkHereInPrivateCode()

1.2.3. Mixin

As stated by Wikipedia: Mixins encourage code reuse and avoid well-known pathologies associated with multiple inheritance.

Mixin is a limited usage of multiple inheritance, but they should not be mixed with overriding.

We use mixins to reuse code, and they provide great help for writing tests.

Methods from a mixin should not be overwritten by classes using the mixin.

Mixins should not overwrite methods or call super().

When defining a class using mixins, put the parent class first, and then add the mixin classes in alphabetical order.

class SomeMixedClass(ParentClass, AnotherMixin, SomeMixin, ZoroMixin):
    """
    A class with `single` inheritance and multiple mixins.
    """

When defining a mixin, document the external class or instance members used by the mixin.

from some_package import complicated_code_using, complicated_other_using


class LoginMixin(object):
    """
    Does some kind of work.

    username - account used for authentication.
    password - password for the account
    ssh_key - SSH key for authentication
    """

    def loginWithUsernameAndPassword(self):
        """
        Does something.
        """
        if self.username:
            raise SomeException()

        complicated_code_using(self.username, self.password)

    def loginWithUsernameAndSSHKey(self):
        """
        Does something else.
        """
        complicated_other_using(self.username, self.ssh_key)

1.2.4. Project specific

  • When default arguments have mutable values, they are defined as None and then assigned the default value.

    Otherwise this can hit us very hard. More details here.

def methodName(self, a, b='imutable', c=None, d=None):
    """
    Describe method.
    """
    if c is None:
       c = []

    if d is None:
       d = SomeObject()

1.2.4.1. Multi line indentation

  • For now, just see some examples:
self.logger.log(
    message_id=factory.number(),
    name=factory.getUniqueString(),
    callback=do_something_else,
    )

Same indentation applies for brackets:

some_list = [
    bla,
    blabla,
    alabala,
    ]
  • Two-line coding exception: If the parentheses expression does not fit on one line:
self.logger.log(
    factory.number(), factory.getUniqueString())
  • Conditional exceptions. When indenting parentheses for conditional expressions add one extra indent to separate the conditional expression from the conditional block.
if (somethin_else is None or
        say_something_else is None):
    do_else = nothing
    do_something()

if (somethin_else is None or
        say_something_else is None or
        we_should_not_have_long_conditionals
        ):
    do_else = nothing
    do_something()
  • Class, method and function indentation.
class MyClassName(
        VeryLongParentClass, VeryLongOtherMixin):
    """
    Docstring here.
    """

def myMethodWithLongArguments(self,
        name=None, other_long_thing=None):
    """
    Docstring here.
    """

def my_function_with_long_arguments(
        name=None,
        other_long_thing=None,
        other_very_long_argument=None,
        ):
    """
    Docstring here.
    """

1.2.4.2. Imports

  • Imports should be called at the start of each module, the only exception allowed is when used for avoiding circular imports.
  • There is one empty line between the import block and the module comment.
  • The import blocks are separated by one empty line.
  • They will be arranged in 3 major blocks:
    • 1 - Importing from Python standard modules.
    • 2 - Importing from modules outside of the project (3rd party).
    • 3 - Importing from modules belonging to the project.
  • In each block, the modules are sorted in alphabetical order, case-insensitive.
  • When importing multiple members of a module, if they don’t exceed the 79 characters limit, they will be listed on the same line
  • When importing multiple members of a module, and they exceed the 79 characters limit, they will be listed as a list, with each member on a line ending with comma.

A good example:

# Copyright (c) YEAR Your Name.
# See LICENSE for details.
"""Sample module for demonstrating imports coding conventions."""
from __future__ import with_statement

from optparse import OptionParser
import logging
import os
import sys
import time
import types

from OpenSSL import crypto
from twisted.web import server
import simplejson as json

from chevah.commons.utils.constants import (
    DEFAULT_KEY_SIZE,
    DEFAULT_KEY_TYPE,
    DEFAULT_PUBLIC_KEY_EXTENSION,
    )
from chevah.commons.utils.exceptions import OperationalException
from chevah.commons.utils.crypto import Key
from chevah.commons.utils.helpers import (
    _,
    log_add_default_handlers,
    open_local_admin_webpage,
    )
from chevah.server.commons.configuration import (
    ApplicationConfiguration,
    )
from chevah.server.commons.constants import (
    CONFIGURATION_SERVER_FILE,
    CONFIGURATION_PID_FILE,
    PRODUCT_NAME,
    )
from chevah.server.commons.process import ChevahTwistedProcess