projectrules.ai

Python Design Patterns Standards

PythonDesign PatternsSoftware ArchitectureProgramming StandardsBest Practices

Description

Standards for Python design patterns implementation

Globs

**/*.py
---
description: Standards for Python design patterns implementation
globs: **/*.py
---

# Python Design Patterns Standards

<rule>
name: python_design_patterns
description: Guidelines for implementing common design patterns in Python
filters:
  - type: file_extension
    pattern: "\.py$"

actions:
  - type: suggest
    message: |
      ## Python Design Patterns Guidelines

      ### Creational Patterns
      1. Singleton:
         ```python
         class Singleton:
             _instance = None

             def __new__(cls, *args, **kwargs):
                 if cls._instance is None:
                     cls._instance = super().__new__(cls)
                 return cls._instance
         ```
         - Use sparingly - often better alternatives exist
         - Consider using module-level variables instead
         - For thread safety, use locks or metaclasses

      2. Factory Method:
         ```python
         class Creator:
             def factory_method(self):
                 raise NotImplementedError

             def operation(self):
                 product = self.factory_method()
                 return product.operation()

         class ConcreteCreator(Creator):
             def factory_method(self):
                 return ConcreteProduct()
         ```
         - Use when object creation logic should be separate from usage
         - Consider using class methods as factories
         - Leverage Python's dynamic typing for flexibility

      3. Builder:
         ```python
         class Product:
             def __init__(self):
                 self.parts = []

         class Builder:
             def __init__(self):
                 self.product = Product()

             def build_part_a(self):
                 self.product.parts.append("Part A")
                 return self

             def build_part_b(self):
                 self.product.parts.append("Part B")
                 return self

             def get_result(self):
                 return self.product
         ```
         - Use for complex object construction
         - Consider using method chaining for fluent interfaces
         - In Python, often simplified with keyword arguments

      ### Structural Patterns
      1. Adapter:
         ```python
         class Target:
             def request(self):
                 pass

         class Adaptee:
             def specific_request(self):
                 return "Specific behavior"

         class Adapter(Target):
             def __init__(self, adaptee):
                 self.adaptee = adaptee

             def request(self):
                 return f"Adapted: {self.adaptee.specific_request()}"
         ```
         - Use to make incompatible interfaces work together
         - Consider using multiple inheritance when appropriate
         - Can be implemented with functions in Python

      2. Decorator:
         ```python
         class Component:
             def operation(self):
                 pass

         class ConcreteComponent(Component):
             def operation(self):
                 return "Basic operation"

         class Decorator(Component):
             def __init__(self, component):
                 self.component = component

             def operation(self):
                 return self.component.operation()

         class ConcreteDecorator(Decorator):
             def operation(self):
                 return f"Enhanced: {super().operation()}"
         ```
         - Use Python's built-in decorator syntax when possible
         - Consider using functools.wraps for function decorators
         - Class decorators can modify class behavior

      3. Composite:
         ```python
         from abc import ABC, abstractmethod

         class Component(ABC):
             @abstractmethod
             def operation(self):
                 pass

         class Leaf(Component):
             def operation(self):
                 return "Leaf operation"

         class Composite(Component):
             def __init__(self):
                 self.children = []

             def add(self, component):
                 self.children.append(component)

             def operation(self):
                 results = []
                 for child in self.children:
                     results.append(child.operation())
                 return f"Composite: {', '.join(results)}"
         ```
         - Use for tree-like object structures
         - Consider using collections.abc interfaces
         - Implement appropriate iteration methods

      ### Behavioral Patterns
      1. Observer:
         ```python
         class Subject:
             def __init__(self):
                 self._observers = []

             def attach(self, observer):
                 self._observers.append(observer)

             def detach(self, observer):
                 self._observers.remove(observer)

             def notify(self):
                 for observer in self._observers:
                     observer.update(self)

         class Observer:
             def update(self, subject):
                 pass
         ```
         - Consider using Python's built-in Observer pattern alternatives:
           - Event hooks
           - Callback functions
           - Signal/slot mechanisms (e.g., PyQt)

      2. Strategy:
         ```python
         class Context:
             def __init__(self, strategy):
                 self._strategy = strategy

             def set_strategy(self, strategy):
                 self._strategy = strategy

             def execute_strategy(self, data):
                 return self._strategy.execute(data)

         class Strategy:
             def execute(self, data):
                 pass
         ```
         - In Python, often simplified using callable objects or functions
         - Consider using function parameters instead of strategy classes
         - Leverage higher-order functions when appropriate

      3. Command:
         ```python
         class Command:
             def execute(self):
                 pass

         class ConcreteCommand(Command):
             def __init__(self, receiver):
                 self._receiver = receiver

             def execute(self):
                 self._receiver.action()

         class Invoker:
             def __init__(self):
                 self._command = None

             def set_command(self, command):
                 self._command = command

             def execute_command(self):
                 self._command.execute()
         ```
         - In Python, often simplified using callable objects
         - Consider using higher-order functions
         - Use for undo/redo functionality or command queuing

      ### Python-Specific Patterns
      1. Context Manager:
         ```python
         class ResourceManager:
             def __init__(self, resource):
                 self.resource = resource

             def __enter__(self):
                 # Setup code
                 return self.resource

             def __exit__(self, exc_type, exc_val, exc_tb):
                 # Cleanup code
                 self.resource.close()
                 return False  # Re-raise exceptions
         ```
         - Use for resource management (files, connections, locks)
         - Consider using contextlib.contextmanager for function-based approach
         - Always handle exceptions properly in __exit__

      2. Descriptor:
         ```python
         class Descriptor:
             def __get__(self, instance, owner):
                 # Return computed or stored value

             def __set__(self, instance, value):
                 # Validate and store value

             def __delete__(self, instance):
                 # Cleanup
         ```
         - Use for attribute access control
         - Consider using property for simple cases
         - Useful for validation, caching, and computed attributes

      ### Anti-Patterns to Avoid
      1. God Object:
         - Classes that know or do too much
         - Violates single responsibility principle
         - Refactor into smaller, focused classes

      2. Spaghetti Code:
         - Tangled, unstructured code flow
         - Use proper function decomposition
         - Follow @python-code-organization guidelines

      3. Reinventing the Wheel:
         - Reimplementing standard library functionality
         - Use built-in modules and functions
         - Leverage established third-party packages

      ### Related Guidelines
      - For code organization, see @python-code-organization
      - For refactoring techniques, see @python-refactoring
      - For API design, see @python-api-design

examples:
  - input: |
      # Bad: Complex creation logic mixed with business logic
      def process_data(data_type, source):
          if data_type == "csv":
              processor = CSVProcessor(source)
          elif data_type == "json":
              processor = JSONProcessor(source)
          elif data_type == "xml":
              processor = XMLProcessor(source)
          else:
              raise ValueError(f"Unsupported data type: {data_type}")

          return processor.process()

      # Good: Factory pattern
      class ProcessorFactory:
          @classmethod
          def create(cls, data_type, source):
              processors = {
                  "csv": CSVProcessor,
                  "json": JSONProcessor,
                  "xml": XMLProcessor
              }

              processor_class = processors.get(data_type)
              if not processor_class:
                  raise ValueError(f"Unsupported data type: {data_type}")

              return processor_class(source)

      # Usage
      processor = ProcessorFactory.create(data_type, source)
      result = processor.process()

      # Bad: Resource management without context manager
      def process_file(path):
          f = open(path, 'r')
          try:
              data = f.read()
              return process_data(data)
          finally:
              f.close()

      # Good: Using context manager
      def process_file(path):
          with open(path, 'r') as f:
              data = f.read()
              return process_data(data)

metadata:
  priority: high
  version: 1.0
  tags: ["python", "design-patterns", "architecture", "best-practices"]
</rule>
Python Design Patterns Standards