Python Guideline

We follow loosely Google Python Style Guide and PEP-8. If you are new at The Gang, we recommend that you read the Google Python Style Guide first. Please refer to the two guidelines for anything not explicitly stated below.

Variable Naming

Variable Naming

Follow PEP-8. Use snake_case for variable and function name. Use camelCase only for class name and ALL_CAP for constants. Ex: Do use snake_case instead of camelCase.

Collection and Iterators

Use plural for collections(list, dict, etc) and iterators. Ex: Do use indices = [1,2,3] instead of index = [1,2,3]

Function Naming

Name the function it so that user know at first glance what it does without having to read doc.

Ex: Do use find_largest_number_less_than(lst, threshold) instead of find_number(list, threshold)

Linting

You must lint your code before committing to the repository. You are going to miss a space or two. So, get your editor or linter to do it. For example, in pycharm, the shortcut is Command+Alt+L or Ctrl+Alt+L. For jupyter notebook, install notebook extensions and enable autopep8. Then click the gear icon on your toolbar to format.

Bad

a=a+ b

Good

a = a + b # linting will do this for you

Typing Annotation

It is highly recommend that you document the type of arguments using mypy. This will make your autocomplete life much better. PyCharm will recognize the typing system and will

The point here is to help you with auto completion. There is really no need to run mypy type check since the checker is not very good with contravariant and such.

Bad

def add_number_to_list(lst, number):
    return [x + number for x in lst]

Good

def add_number_to_list(lst: List[float], number: float) -> List[float]:
    return [x + number for x in lst]

Write Test

At The Gang, the code base for each project tends to be quite big with interworking pieces. Changing one file may affect other people’s work. To make everyone life easier, it is essential to write unit test at least to make sure that it doesn’t crash. The recommended tool is pytest. Readup how to use it. Or ask your project advisor how to do it.

DocString

Please use Google Style Doc String. See an example below. Much more readable than RST style. To have pycharm do this automatically search for google in pycharm setting, in the python integrated tool change docstring format from rst to Google.

def add_number_to_list(lst: List[float], number: float) -> List[float]:
    """Add number to list
        Args:
            lst (List[float]): list of numbers to be added.
            number (float): number to add.

        Returns:
            List[float]. List of added numbers.
    """
    return [x + number for x in lst]

Return Object Instead of Long Tuple

Being able to return multiple objects and use tuple expansion is great but once you return more than 2 objects you will start questioning yourself which order you need it tobe. So instead of return a long tuple, return an object instead. We could live with 1 or 2 objects but 3 and more is a No No.

Bad

def summarize(arr: np.ndarray) -> Tuple[float, float, int]:
    return np.average(arr), np.std(arr), len(arr)

Good

@dataclass
class SummarizeResult:
    mean: float
    std: float
    size: int

    def __iter__(self): # Optional just in case you still want to do tuple expansion
        yield mean
        yield std
        yield size

def summarize(arr: np.ndarray) -> SummarizeResult:
    return SummarizeResult(
        mean = np.average(arr),
        std = np.std(arr),
        size = len(arr)
    )

Use Named Parameter

If you need to call a function with a long list of parameters. DO NOT rely on the ordering, someone may change it. Also it’s so hard to figure out which parameter is which. Instead, call it with named parameters. For example,

def make_tea(tea_type: str, boba_type: str, boba_number: int, sugar_amount: float) -> Tea:
    pass

Bad

make_tea('green', 'coffee', 30, 2.5)

Good

make_tea(tea_type='green',
         boba_type='coffee",
         boba_number=10,
         sugar_amount=3.0)

Class Constructor

It is recommended that you use dataclass if possible. If you need to use the old style class with __init__ then your main constructor must be dumb that means all it does is copy the arguments to self.xxx. Then, complicated constructor which requires logic should be in an alternative constructor class method. This allows your code to be testable without much dependency on other objects.

Bad, but Tempting

class MetaData:
    def __init__(self, file: FileObject):
        self.size = file.size
        self.is_file = os.is_file(file)

Good

class MetaData
    def __init__(self, size: int, is_file: bool):
        self.size = size
        self.is_file = is_file

    @classmethod
    def from_file(cls, file: FileObject) -> 'MetaData':
        # the single quote in the return type is for forward referencing
        return MetaData(size=size, is_file=os.is_file(file))

Better

@dataclass
class MetaData:
    size: int
    is_file: bool

    @classmethod
    def from_file(cls, file: FileObject) -> 'MetaData':
        return MetaData(size=size, is_file=os.is_file(file))

Use Context Manager for Opening File

Use with when opening a file. Avoid using try except finally when open file for read/write. It’s much easier to read and most likely you will forgot to do except.

Bad

try:
    f = open('hello.txt')
    print(f.read())
finally:
    f.close()

Good

with open('hello.txt') as f:
    print(f.read())

If one need to pass the opened file around, we can use contextlib. For example,

from contextlib import contextmanager

@contextmanager
def get_data_file():
    with open('data.txt') as f:
        yield f

def use_data():
    with get_data_file() as f:
        print(f.read())

For more information on contextlib, see context manager.