Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Table of Contents

...

Introduction

...

Why Python?

...

General Philosophy

...

PEP 8 Highlights

...

General Coding Standards

...

File layout

...

Object-Oriented Programming (OOP) Coding Standards

...

Package / Module Structuring

...

OOP Abstract Base Class (ABC) Coding Standards

...

Conclusion

Introduction

The Sibros Python coding standards serves as a general programming guideline for a variety of aspects such as style guide, conventions, and design. The guidelines extend beyond Python's PEP 8 style guide, and just like PEP 8, the guidelines only serve as recommendations.

Why Python?

Python is an excellent programming language that has multiple advantages over other languages.

Easy to learn

Python was designed to be written closer to English by simplifying the overall syntax; it generally takes less lines of code to achieve similar functionality in other languages like C or C++.

Great for prototyping

Because of Python's simplistic nature, it has a quick turnaround time to create programs for a wide range of applications ranging from internal/external standalone tools to simple scripts for automation.

Object-oriented

Python is an object-oriented language, so developers can take advantage of object-oriented design to create complex yet elegant designs.

General Philosophy

Readability vs. performance

The Sibros Python coding standards serves as a general programming guideline for a variety of aspects such as style guide, conventions, and design. The guidelines extend beyond Python's PEP 8 style guide, and just like PEP 8, the guidelines only serve as recommendations.

...

Why Python?

Python is an excellent programming language that has multiple advantages over other languages.

Easy to learn

Python was designed to be written closer to English by simplifying the overall syntax; it generally takes less lines of code to achieve similar functionality in other languages like C or C++.

Great for prototyping

Because of Python's simplistic nature, it has a quick turnaround time to create programs for a wide range of applications ranging from internal/external standalone tools to simple scripts for automation.

Object-oriented

Python is an object-oriented language, so developers can take advantage of object-oriented design to create complex yet elegant designs.

...

General Philosophy

Readability vs. performance

Python should be written with readability as the utmost priority; optimization is secondary. Optimization should only be done if the problem being solved is proven to be a problem in the first place. See Premature Optimization for more details.

...

Code Block
from coordinate import Coordinate

# Good: Name is derived from the type Coordinate
coordinate = Coordinate()

# Good: Name is suffixed with the type and overall has more context (represents coordinate of a vehicle)
vehicle_coordinate = Coordinate()

# Bad: Name deviates from the type Coordinate; this will confuse other readers
location = Coordinate()

# Good: Name denotes that the variable is a pair (tuple) and is explicit about the data structure
latitude_longitude_pair = (coordinate.latitude, coordinate.longitude)

# Debatable: Sometimes names may be too long or too detailed. It may already be implied that latitude and longitude are best represented as floats
latitude_longitude_float_pair = (coordinate.latitude, coordinate.longitude)
coordinate = Coordinate()

# Good: Plural name clearly implies a list of coordinate objects
coordinates = [
    coordinate,
]

# Somewhat good: Although the suffix "dict" makes it clear that this is a dictionary, readers also need context on the dictionary's schema
coordinate_dict = {
    "current": coordinate
}

# Good: Suffix makes it clear that this is a set (unique set of coordinates)
coordinate_set = {
    coordinate
}

# Good: Suffix suffix "dict" makes it clear that this is a string representing a coordinate
coordinate_str = "0.0, 0.0"

Sometimes it is fine to assume the name is implicitly clear if there is enough context surrounding the names (this is mostly applicable for well-known conventions or is typically the case when the readers have domain knowledge).

Code Block
from memoryblock import MemoryBlock

# Memory block represents a simple block of memory starting at some address of some size
memory_block = MemoryBlock()
memory_block.name  # Assume this attribute is a string representing the name of the memory block
memory_block.address  # Assume this attribute is an integer representing the start address
memory_block.size  # Assume this attribute is an integer representing the size in bytes

Filesystem

Python developers will often deal with the filesystem in some form; some applications that rely on the filesystem include general automation, file/directory manipulation, and command line tool.

Fundamental concepts

It is important to understand the fundamentals of filesystem such as:

Here are some guidelines:

  • Programs should assume the current directory can be anywhere (i.e. do not assume current directory will be in the same directory as the entry point file)

  • Programs should generally work regardless if given absolute paths or a relative paths (it depends on how the paths are provided)

  • Programs should be OS agnostic and work regardless if dealing with POSIX paths or Windows paths

Other

Apart from precondition checks at the beginning of a function/method, each function/method should only have one return statement and that should be at the end of the function. This is to prevent unintentional side-effects where a developer adds some code thinking it will be run, but the function exits out before it gets to that piece of code.

Naming convention

Similar to the general naming convention in the earlier section, filesystem paths should have its own naming convention. The name of the variable should represent a filesystem node file vs. directory and should be explicit about the representation name vs. path vs. name with no extension.

Here are some naming guidelines:

  • Variable representing a directory path should be suffixed with dirpath or dirname

  • Variable representing a file path should be suffixed with filepath or filename

  • Variable representing any path should be suffixed with nodepath or nodename

Example

Code Block
from datetime import datetime
import os
from pathlib import Path

# Good: It is clear that this variable represents a directory containing N log files
log_dirpath = Path("workspace/logs")

# Bad: "logs" can easily be misinterpreted as a list of log objects
logs = Path("workspace/logs")

timestamp_str = datetime.now().strftime("%m-%d-%Y_%H-%M-%S")

# Good: It is clear that this variable represents a file path relative to the current directory
log_filepath = Path("log_{}.txt".format(timestamp_str))

# Good: It is clear that this variable represents a file name and does not represent an addressable path by itself
log_filename = "log_{}.txt".format(timestamp_str)

# Good: The suffix nodepath implicitly denotes that this function takes file or directory paths
def copy(src_nodepath, dest_nodepath):
    """
    Copy a source path to the destination path
    If the given paths are directory paths, perform a recursive copy
    """
    raise NotImplementedError

# Bad: The name "file" can easily be misinterpreted as a file object.
# The function "os.listdir" actually returns a list of node names (names of both directories and files)
for file in os.listdir(log_dirpath):
    print(file)

Current Directory

From a program’s perspective, the current directory can be anywhere in the filesystem; it is up to the caller of the program.

Code Block
languagebash
./program.py                     # Current directory is in the same directory as the program file
./directory/program.py           # Current directory is at the parect directory of the program file
/home/root/directory/program.py  # Current directory could be anywhere in the filesystem

It is good practice to ensure your program is agnostic to the location of the current directory. Some good ways to make your program agnostic of the current directory is to:

  1. Derive the parent directory of the program file, then assemble paths relative to the location of the program file itself

  2. Let the caller determine the location of a directory/file by taking path(s) as commend line option(s). This avoids having your program make assumptions of the host’s filesystem layout

Code Block
languagepy
"""
Example, derive the parent directory of this file, then assemble paths relative to the parent directory

This approach is great if this program is packaged with other files that live alongside it (e.g. configuration files)
"""

import os
from pathlib import Path

# Ways to get the parent directory path of this program file
self_dirpath = os.path.dirname(__file__)  # Using traditional os to get a string object
self_dirpath = Path(__file__).parent  # Using modern pathlib to get a Path object

# Ways to assemble a path relative to the parent directory of this file
target_filepath = os.path.join(self_dirpath, "directory", "test.txt")  # Assume self_dirpath is a string object
target_filepath = self_dirpath.joinpath("directory", "test.txt")  # Assume self_dirpath is a Path object
Code Block
languagepy
"""
Example, let the caller of this program provide the path to use

This example uses pathlib Path objects instead of os string objects
"""

from argparse import ArgumentParser
from pathlib import Path
import sys


def get_args():
    argument_parser = ArgumentParser()
    argument_parser.add_argument("-d", "--target-dirpath", type=Path, help="A directory path to reference")
    return argument_parser.parse_args()
    

def main():
    args = get_args()
    target_filepath = args.target_dirpath.joinpath("directory", "test.txt")
    
  
if __name__ == "__main__":
    sys.exit(main())

Changing the Current Directory

Your program (whether it’s Python or a shell script) should avoid changing the caller’s current directory if possible.

Code Block
languagebash
/home/user$ ./some-blackbox-program      # <=== Program changes the current directory during execution
/tmp$                                    # <=== Program exited; current directory changed (Avoid this behavior if possible)

If the current directory needs to be changed for any reason, ensure it gets reverted by the end of the program.

Code Block
languagepy
import os

prev_current_directory = os.getcwd()
try:
    os.chdir("/tmp")
    # Do stuff here
finally:
    os.chdir(prev_current_directory)dictionary, readers also need context on the dictionary's schema
coordinate_dict = {
    "current": coordinate
}

# Good: Suffix makes it clear that this is a set (unique set of coordinates)
coordinate_set = {
    coordinate
}

# Good: Suffix makes it clear that this is a string representing a coordinate
coordinate_str = "0.0, 0.0"

Sometimes it is fine to assume the name is implicitly clear if there is enough context surrounding the names (this is mostly applicable for well-known conventions or is typically the case when the readers have domain knowledge).

Code Block
from memoryblock import MemoryBlock

# Memory block represents a simple block of memory starting at some address of some size
memory_block = MemoryBlock()
memory_block.name  # Assume this attribute is a string representing the name of the memory block
memory_block.address  # Assume this attribute is an integer representing the start address
memory_block.size  # Assume this attribute is an integer representing the size in bytes

Filesystem

Python developers will often deal with the filesystem in some form; some applications that rely on the filesystem include general automation, file/directory manipulation, and command line tool.

Fundamental concepts

It is important to understand the fundamentals of filesystem such as:

Here are some guidelines:

  • Programs should assume the current directory can be anywhere (i.e. do not assume current directory will be in the same directory as the entry point file)

  • Programs should generally work regardless if given absolute paths or a relative paths (it depends on how the paths are provided)

  • Programs should be OS agnostic and work regardless if dealing with POSIX paths or Windows paths

Current Directory

From a program’s perspective, the current directory can be anywhere in the filesystem; it is up to the caller of the program.

Code Block
languagebash
./program.py                     # Current directory is in the same directory as the program file
./directory/program.py           # Current directory is at the parect directory of the program file
/home/root/directory/program.py  # Current directory could be anywhere in the filesystem

It is good practice to ensure your program is agnostic to the location of the current directory. Some good ways to make your program agnostic of the current directory is to:

  1. Derive the parent directory of the program file, then assemble paths relative to the location of the program file itself

  2. Let the caller determine the location of a directory/file by taking path(s) as commend line option(s). This avoids having your program make assumptions of the host’s filesystem layout

Code Block
languagepy
"""
Example, derive the parent directory of this file, then assemble paths relative to the parent directory

This approach is great if this program is packaged with other files that live alongside it (e.g. configuration files)
"""

import os
from pathlib import Path

# Ways to get the parent directory path of this program file
self_dirpath = os.path.dirname(__file__)  # Using traditional os to get a string object
self_dirpath = Path(__file__).parent  # Using modern pathlib to get a Path object

# Ways to assemble a path relative to the parent directory of this file
target_filepath = os.path.join(self_dirpath, "directory", "test.txt")  # Assume self_dirpath is a string object
target_filepath = self_dirpath.joinpath("directory", "test.txt")  # Assume self_dirpath is a Path object
Code Block
languagepy
"""
Example, let the caller of this program provide the path to use

This example uses pathlib Path objects instead of os string objects
"""

from argparse import ArgumentParser
from pathlib import Path
import sys


def get_args():
    argument_parser = ArgumentParser()
    argument_parser.add_argument("-d", "--target-dirpath", type=Path, help="A directory path to reference")
    return argument_parser.parse_args()
    

def main():
    args = get_args()
    target_filepath = args.target_dirpath.joinpath("directory", "test.txt")
    
  
if __name__ == "__main__":
    sys.exit(main())

Changing the Current Directory

Your program (whether it’s Python or a shell script) should avoid changing the caller’s current directory if possible.

Code Block
languagebash
/home/user$ ./some-blackbox-program      # <=== Program changes the current directory during execution
/tmp$                                    # <=== Program exited; current directory changed (Avoid this behavior if possible)

If the current directory needs to be changed for any reason, ensure it gets reverted by the end of the program.

Code Block
languagepy
import os

prev_current_directory = os.getcwd()
try:
    os.chdir("/tmp")
    # Do stuff here
finally:
    os.chdir(prev_current_directory)

Naming convention

Similar to the general naming convention in the earlier section, filesystem paths should have its own naming convention. The name of the variable should represent a filesystem node file vs. directory and should be explicit about the representation name vs. path vs. name with no extension.

Here are some naming guidelines:

  • Variable representing a directory path should be suffixed with dirpath or dirname

  • Variable representing a file path should be suffixed with filepath or filename

  • Variable representing any path should be suffixed with nodepath or nodename

Example

Code Block
from datetime import datetime
import os
from pathlib import Path

# Good: It is clear that this variable represents a directory containing N log files
log_dirpath = Path("workspace/logs")

# Bad: "logs" can easily be misinterpreted as a list of log objects
logs = Path("workspace/logs")

timestamp_str = datetime.now().strftime("%m-%d-%Y_%H-%M-%S")

# Good: It is clear that this variable represents a file path relative to the current directory
log_filepath = Path("log_{}.txt".format(timestamp_str))

# Good: It is clear that this variable represents a file name and does not represent an addressable path by itself
log_filename = "log_{}.txt".format(timestamp_str)

# Good: The suffix nodepath implicitly denotes that this function takes file or directory paths
def copy(src_nodepath, dest_nodepath):
    """
    Copy a source path to the destination path
    If the given paths are directory paths, perform a recursive copy
    """
    raise NotImplementedError

# Bad: The name "file" can easily be misinterpreted as a file object.
# The function "os.listdir" actually returns a list of node names (names of both directories and files)
for file in os.listdir(log_dirpath):
    print(file)

Other

Apart from precondition checks at the beginning of a function/method, each function/method should only have one return statement and that should be at the end of the function. This is to prevent unintentional side-effects where a developer adds some code thinking it will be run, but the function exits out before it gets to that piece of code.

...

File layout

Defining a convention for file layouts is important for consistency. All Python files should have a defined purpose, and Python files should not be created simply for the sake of dividing a program into different modules.

...