xbe.at

Python / Testing and best practices

Best Practices for Naming in Python

6 min read, 14 sections

Contents
  1. Clear and Descriptive Variable Names
  2. Function Naming Patterns
  3. Class Naming Conventions
  4. Constants and Module-Level Variables
  5. Iterator and Loop Variable Naming
  6. Real-world Example - Data Processing Pipeline
  7. Naming in Machine Learning Contexts
  8. Semantic Naming in Data Structures
  9. Real-world Example - Time Series Analysis
  10. Exception Handling and Error Messages
  11. Interface Design and Method Names
  12. Namespaces and Module Organization
  13. Mathematics and Algorithm Implementation
  14. Additional Resources

Clear and Descriptive Variable Names

Good variable names should be descriptive, meaningful, and follow Python naming conventions. They should clearly indicate the purpose and content of the variable while maintaining readability.

# Bad naming
x = ['apple', 'banana', 'orange']
n = len(x)
r = []
for i in range(n):
    r.append(x[i].upper())

# Good naming
fruits = ['apple', 'banana', 'orange']
fruits_count = len(fruits)
uppercase_fruits = []
for index in range(fruits_count):
    uppercase_fruits.append(fruits[index].upper())

print(uppercase_fruits)  # Output: ['APPLE', 'BANANA', 'ORANGE']

Function Naming Patterns

Function names should be action-oriented, using verbs to describe their purpose. They should follow the snake_case convention and clearly indicate the transformation or computation being performed.

# Bad naming
def proc(lst):
    return [x for x in lst if x > 0]

# Good naming
def filter_positive_numbers(number_list):
    return [num for num in number_list if num > 0]

numbers = [-2, 0, 3, -1, 5]
result = filter_positive_numbers(numbers)
print(result)  # Output: [3, 5]

Class Naming Conventions

Classes represent objects and should use PascalCase naming convention. The name should be a noun that clearly describes the entity being modeled, with clear and descriptive attribute names.

# Bad naming
class calc:
    def __init__(self, x, y):
        self.a = x
        self.b = y
    
    def p(self):
        return self.a + self.b

# Good naming
class Calculator:
    def __init__(self, first_number, second_number):
        self.first_number = first_number
        self.second_number = second_number
    
    def calculate_sum(self):
        return self.first_number + self.second_number

calc = Calculator(5, 3)
print(calc.calculate_sum())  # Output: 8

Constants and Module-Level Variables

Constants should use uppercase letters with underscores, placed at the module level. Their names should clearly indicate their purpose and usage context.

# Bad naming
x = 3.14159
max_s = 100
def_timeout = 30

# Good naming
PI = 3.14159
MAX_CONNECTIONS = 100
DEFAULT_TIMEOUT_SECONDS = 30

def calculate_circle_area(radius):
    return PI * radius ** 2

print(calculate_circle_area(5))  # Output: 78.53975

Iterator and Loop Variable Naming

When working with iterators and loops, use meaningful names that represent the individual elements being processed, avoiding single-letter variables except for simple mathematical operations.

# Bad naming
for x in range(len(l)):
    for y in l[x]:
        print(y)

# Good naming
matrix = [[1, 2, 3], [4, 5, 6]]
for row_index in range(len(matrix)):
    for element in matrix[row_index]:
        print(f"Processing element: {element}")

Real-world Example - Data Processing Pipeline

A practical example demonstrating naming conventions in a data processing pipeline for customer transaction analysis.

class TransactionProcessor:
    def __init__(self, transaction_data):
        self.transaction_data = transaction_data
        self.processed_transactions = []
        
    def filter_valid_transactions(self, minimum_amount=0):
        return [
            transaction for transaction in self.transaction_data
            if transaction['amount'] > minimum_amount
        ]
    
    def calculate_total_revenue(self, transactions):
        return sum(transaction['amount'] for transaction in transactions)

# Example usage
daily_transactions = [
    {'id': 1, 'amount': 100.0, 'customer': 'John'},
    {'id': 2, 'amount': -50.0, 'customer': 'Alice'},
    {'id': 3, 'amount': 75.0, 'customer': 'Bob'}
]

processor = TransactionProcessor(daily_transactions)
valid_transactions = processor.filter_valid_transactions()
total_revenue = processor.calculate_total_revenue(valid_transactions)
print(f"Total Revenue: ${total_revenue}")  # Output: Total Revenue: $175.0

Naming in Machine Learning Contexts

Machine learning variable and function names should reflect the mathematical concepts while remaining readable. Common conventions include using lowercase for vectors and uppercase for matrices.

import numpy as np

class NeuralNetwork:
    def __init__(self, input_size, hidden_size, output_size):
        # Weight matrices use uppercase, biases lowercase
        self.W1 = np.random.randn(input_size, hidden_size)
        self.W2 = np.random.randn(hidden_size, output_size)
        self.b1 = np.zeros((1, hidden_size))
        self.b2 = np.zeros((1, output_size))
    
    def forward_propagation(self, input_data):
        self.hidden_layer = np.tanh(np.dot(input_data, self.W1) + self.b1)
        self.output_layer = np.dot(self.hidden_layer, self.W2) + self.b2
        return self.output_layer

# Example usage
model = NeuralNetwork(3, 4, 2)
sample_input = np.array([[1.0, 0.5, -0.2]])
prediction = model.forward_propagation(sample_input)

Semantic Naming in Data Structures

When implementing data structures, names should reflect the structure's purpose and behavior, making the code self-documenting and easier to maintain.

class BinarySearchTree:
    def __init__(self, value):
        self.value = value
        self.left_child = None
        self.right_child = None
    
    def insert_node(self, new_value):
        if new_value < self.value:
            if self.left_child is None:
                self.left_child = BinarySearchTree(new_value)
            else:
                self.left_child.insert_node(new_value)
        else:
            if self.right_child is None:
                self.right_child = BinarySearchTree(new_value)
            else:
                self.right_child.insert_node(new_value)

# Usage example
root = BinarySearchTree(10)
root.insert_node(5)
root.insert_node(15)

Real-world Example - Time Series Analysis

A comprehensive example showing naming conventions in time series data processing and analysis implementation.

class TimeSeriesAnalyzer:
    def __init__(self, time_series_data):
        self.raw_data = time_series_data
        self.processed_data = None
        self.seasonal_components = None
        
    def remove_outliers(self, window_size=5, threshold=2):
        rolling_mean = np.mean(self.raw_data)
        rolling_std = np.std(self.raw_data)
        z_scores = np.abs((self.raw_data - rolling_mean) / rolling_std)
        self.processed_data = self.raw_data[z_scores < threshold]
        
    def extract_seasonality(self, period_length):
        if self.processed_data is None:
            self.processed_data = self.raw_data
        self.seasonal_components = np.array([
            np.mean(self.processed_data[i::period_length])
            for i in range(period_length)
        ])
        return self.seasonal_components

# Example usage
monthly_temperatures = np.array([20, 22, 25, 28, 30, 32, 31, 29, 26, 23, 21, 19])
analyzer = TimeSeriesAnalyzer(monthly_temperatures)
seasonal_pattern = analyzer.extract_seasonality(period_length=12)

Exception Handling and Error Messages

Error messages and exception names should be clear, specific, and provide actionable information. Names should indicate the type of error and its context.

class DatabaseConnectionError(Exception):
    """Custom exception for database connection failures"""
    pass

class DataValidator:
    def __init__(self, connection_string):
        self.connection_string = connection_string
        
    def validate_user_input(self, user_data):
        required_fields = {'username', 'email', 'age'}
        missing_fields = required_fields - set(user_data.keys())
        
        if missing_fields:
            raise ValueError(
                f"Missing required fields: {', '.join(missing_fields)}"
            )
        
        if not isinstance(user_data['age'], int):
            raise TypeError(
                f"Age must be an integer, got {type(user_data['age']).__name__}"
            )

# Example usage
validator = DataValidator("postgresql://localhost:5432/mydb")
try:
    validator.validate_user_input({'username': 'john', 'email': '[email protected]'})
except ValueError as error:
    print(f"Validation error: {error}")

Interface Design and Method Names

Method names in interfaces and abstract classes should clearly communicate their contract and expected behavior, focusing on the action being performed.

from abc import ABC, abstractmethod

class DataProcessor(ABC):
    @abstractmethod
    def preprocess_raw_data(self, data):
        """Transform raw data into processable format"""
        pass
    
    @abstractmethod
    def extract_features(self, preprocessed_data):
        """Extract relevant features from preprocessed data"""
        pass

class ImageProcessor(DataProcessor):
    def preprocess_raw_data(self, image_data):
        self.normalized_image = image_data / 255.0
        return self.normalized_image
    
    def extract_features(self, preprocessed_data):
        # Example feature extraction
        return {
            'mean_intensity': preprocessed_data.mean(),
            'std_intensity': preprocessed_data.std()
        }

processor = ImageProcessor()
sample_data = np.random.rand(28, 28)
features = processor.extract_features(processor.preprocess_raw_data(sample_data))

Namespaces and Module Organization

Well-structured namespaces and module names help organize code logically and prevent naming conflicts while maintaining clear dependencies.

# file: data_processing/transformers.py
class DataTransformer:
    def __init__(self, transformation_type):
        self.transformation_type = transformation_type
    
    def transform(self, data):
        if self.transformation_type == 'normalize':
            return (data - data.mean()) / data.std()
        elif self.transformation_type == 'standardize':
            return (data - data.min()) / (data.max() - data.min())

# file: data_processing/validators.py
class DataValidator:
    @staticmethod
    def check_missing_values(data):
        return data.isnull().sum()

# Usage
from data_processing.transformers import DataTransformer
from data_processing.validators import DataValidator

transformer = DataTransformer('normalize')
validator = DataValidator()

Mathematics and Algorithm Implementation

Mathematical function names should balance clarity with mathematical convention, using descriptive names for implementation details while preserving standard notation.

import numpy as np

def calculate_matrix_operation(X, learning_rate=0.01):
    """
    Implements the following operation:
    $$H = \tanh(XW + b)$$
    $$Y = \text{softmax}(H)$$
    """
    num_samples, num_features = X.shape
    
    # Initialize weights and bias
    weights = np.random.randn(num_features, num_features)
    bias = np.zeros(num_features)
    
    # Forward pass
    hidden_layer = np.tanh(np.dot(X, weights) + bias)
    
    # Softmax calculation
    exp_scores = np.exp(hidden_layer)
    probabilities = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
    
    return probabilities

# Example usage
input_data = np.random.randn(10, 5)
output_probabilities = calculate_matrix_operation(input_data)

Additional Resources

More in Testing and best practices

  1. Comprehensive Python Testing Strategies5 min

    Happy path testing involves testing the primary use case or the most common scenario of an application or a function. It ensures that the code works as...

  2. Advantages of Logging over Print() in Python8 min

    Logging is a powerful tool for tracking events in your Python programs. It offers significant advantages over using print() statements for debugging and...

  3. Debugging Tips for Coding Challenges with Python3 min

    Reproducibility Problem Keep a log of actions when the bug appears to recreate conditions

  4. Effective Resource Management in Python7 min

    Python's memory management system automatically handles object cleanup through garbage collection, but certain system resources like file handles and network...

  5. Exception Chaining in Python Master the Art of Robust Error Handling9 min

    Exception chaining in Python allows developers to maintain the context of original exceptions while raising new ones, creating a traceback chain that...

  6. Exception Handling in Python A Comprehensive Guide8 min

    Exceptions are events that occur during program execution which disrupt the normal flow of instructions. In Python, exceptions are objects that represent...