Python: Style Guide and Linting

A style guide is a set of rules that defines how code should be written. Following a style guide can make code more consistent, easier to read, and easier to maintain.

In this blog post, we will discuss the Python style guide, linting, and its importance.

Python Style Guide is a set of guidelines for Python code formatting and organization aimed at improving the readability, maintainability, and consistency of Python code. Here we cover topics such as indentation, comments, naming conventions, function and class definitions, and more. The guide recommends the use of clear, concise, and expressive code that follows the PEP 8 style guide, and provides specific examples and explanations of common coding patterns and practices. Overall, the Python Style Guide serves as a valuable resource for Python developers looking to write high-quality code that is easy to understand and maintain.

Naming Convention

Here are some common conventions for in Python:

Guidelines derived from Guido’s Recommendations

Type Public Internal
Packages lower_with_under
Modules lower_with_under _lower_with_under
Classes CapWords _CapWords
Exceptions CapWords
Functions lower_with_under() _lower_with_under()
Global/Class Constants CAPS_WITH_UNDER _CAPS_WITH_UNDER
Global/Class Variables lower_with_under _lower_with_under
Instance Variables lower_with_under _lower_with_under (protected)
Method Names lower_with_under() _lower_with_under() (protected)
Function/Method Parameters lower_with_under
Local Variables lower_with_under

Docstring

Docstrings are used to document modules, classes, functions, and methods.

Modules docstrings should be enclosed in triple quotes (""") and placed immediately below the object they are documenting. The docstring should provide a clear and concise description of the object’s purpose and behavior.

For modules and packages, the docstring should describe the contents of the module or package and provide usage examples if appropriate. For classes, the docstring should describe the class’s behavior and list its public methods and attributes. For functions and methods, the docstring should describe the arguments, return values, and any exceptions that may be raised.

Module Docstring

"""This module provides utility functions for working with strings."""

def function1():
  pass

def function2():
  pass

Class Docstring

class MyClass:
  """This is a sample class that demonstrates docstring formatting.

  This class provides a simple example of how to format docstrings for classes.
  """

  def __init__(self):
    pass

  def public_method(self):
    """This is a public method of the class.

    This method does something useful and returns a result.
    """
    pass

Function Docstring

def my_function(arg1, arg2):
  """This function does something.

  Args:
    arg1: The first argument.
    arg2: The second argument.

  Returns:
    The return value. It returns something. 
  """
  pass

Here’s an example of a function docstring with Args , Returns , and Raises sections:

def divide_numbers(numerator: float, denominator: float) -> float:
  """Divides the numerator by the denominator.

  Args:
    numerator: The numerator.
    denominator: The denominator.

  Returns:
    The result of the division.

  Raises:
    ValueError: If the denominator is zero.
  """
  if denominator == 0:
    raise ValueError("Denominator cannot be zero.")
  return numerator / denominator

Static Typing

Use static typing with type annotations to make the code more readable and maintainable. Type annotations help clarify the expected types of arguments and return values, and can also help catch certain types of errors before runtime. Here’s an example of how to use static typing with type annotations:

def add_numbers(x: int, y: int) -> int:
  """Adds two integers together and returns the result."""
  return x + y

In this example, the function add_numbers() takes two arguments of type int , and returns an int . The type annotations are added after the argument and return value names, separated by a colon. Type annotations can be added to variables, function/methods arguments, and return values.

Sure, here are some more examples of how to use static typing with type annotations:

a: int = 42
b: str = "hello"
c: List[int] = [1, 2, 3]

In this example, we declare three variables with type annotations. a is an int , b is a str , and c is a list of int s.

Function Argument and Return Types

def calculate_average(numbers: List[float]) -> float:
  """Calculates the average of a list of numbers."""
  return sum(numbers) / len(numbers)

In this example, the function calculate_average() takes a single argument numbers that is a list of float s, and returns a float representing the average of the list.

Class Attributes and Methods

class Rectangle:
  # No return type required for __init__
  def __init__(self, width: float, height: float):
    self.width = width
    self.height = height
    
  def area(self) -> float:
    return self.width * self.height

In this example, we define a Rectangle class with an __init__ method that takes two arguments, width and height , both of type float . The class also has an area() method that returns the area of the rectangle as a float .

Useful suggestions

  1. Limit all lines to a maximum of 101 characters.
  2. Avoid using single-letter variable names, except for iterators.
  3. Use spaces around operators and after commas, but not directly inside parentheses or brackets.
  4. Use inline comments sparingly and only for clarifying complex code.
  5. Use docstrings to document modules, classes, functions, and methods.
  6. Avoid using global variables whenever possible.
  7. Use is and is not to compare with None, rather than == or !=.
  8. Use list comprehensions and generator expressions instead of filter() and map() functions.
  9. Use the with statement for file operations and other resource-intensive operations.
  10. Use the logging module for logging instead of print().
  11. Use if/raise instead of assert. User assert only in Tests.
  12. Check for conditions that should never occur.
  13. Use pathlib instead of os.path for path manipulation.
  14. Use f-string instead of format/ %. Avoid using % for string formatting, since it’s an older syntax that can be less readable and error-prone.

Linting

Linting refers to the practice of running code analysis tools to flag programming errors, bugs, stylistic errors, and suspicious usage in Python code.

By promoting quality and consistency, linting leads to more robust, maintainable, and bug-free code. The small upfront investment in linting pays huge dividends in the long term in software quality and developer productivity.

Here are some key reasons why linting is an important part of the software development process:

  • Catch bugs and errors early - Linting helps catch syntax errors, undefined variables, type inconsistencies, and other issues that can lead to bugs. Finding issues before code goes to production is hugely valuable.
  • Enforce coding standards - Linters can check code against style guides like PEP8 and highlight deviations. This helps teams keep code consistent and readable.
  • Reduce technical debt - Linting helps avoid accumulating cruft, duplicate code, and messy/outdated patterns that make code harder to maintain. It prevents technical debt.
  • Improve readability - Well-linter code with proper formatting and organization is much easier for developers to parse and understand quickly. Readability matters.
  • Refactoring assistance - Linters can point out complex code and long functions that need to be refactored and simplified. Streamlines maintenance.
  • Analyze program flow - Linters can track variable usage, uncover unreachable code, and analyze control flow to point out logical issues.
  • Integrate with IDEs and CI/CD - Linting can integrate with developer workflows through editor plugins and continuous integration. Automatic linting provides rapid feedback.

Tools for python code linting

  • pylint - This is one of the most widely used linters for Python. It analyzes code for errors, tries to enforce a coding standard, and also looks for code smells. See Pylint docs
  • flake8 - This linter combines the capabilities of PyFlakes (to check for errors), pycodestyle (for style guide enforcement), and Ned Batchelder’s McCabe script (for complexity checking). See flake8 docs
  • pydocstyle - Focuses mainly on linting docstrings based on PEP 257. Helps ensure consistent documentation. See pydocstyle docs
  • mypy - A static type checker that looks for type errors. Can catch many bugs as part of lining. See mypy docs
  • pyreverse - Creates UML diagrams to analyze large codebases. Helps enforce programming principles. See pyreverse docs
  • yapf - Yet Another Python Formatter. Helps enforce coding style and formatting conventions. See yapf docs
  • black - An auto-formatter that can enforce coding style as part of lining. See black docs
  • pyupgrade - Helps upgrade Python syntax to newer versions automatically. Catches outdated code patterns. See pyupgrade docs
  • bandit - Focuses on security issues by scanning for common vulnerabilities and exposures in Python code. See bandit docs
  • isort - Helps enforce import sorting to group imports alphabetically and separated by type. Improves readability. See isort docs