Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Advanced Python: Design Patterns, Exception Handling, and Module Management

Tech May 9 3

Object Instantiation Mechanics

In Python, object creation is a two-step process involving __new__ and __init__. The __new__ method is responsible for allocating memory and returning a new instance, while __init__ handles the initialization of that instance's attributes. When overriding __new__, you must explicitly call the parent class's __new__ to ensure proper memory allocation, and it must return the newly created instance. Conversely, __init__ receives the instance as self, modifies its state, and implicitly returns None.

Factory Design Patterns

Directly instantiating concrete classes within application code creates tight coupling. Factory patterns abstract this process to improve maintainability.

Simple Factory

A simple factory centralizes object creation logic. Instead of scattering instantiation code, a dedicated function or class handles it based on input parameters.

class CreditPayment:
    def process(self):
        return "Processing credit payment"

class PayPalPayment:
    def process(self):
        return "Processing PayPal payment"

class PaymentFactory:
    @staticmethod
    def create_payment(method: str):
        if method == "credit":
            return CreditPayment()
        elif method == "paypal":
            return PayPalPayment()
        raise ValueError("Unknown payment method")

# Usage
processor = PaymentFactory.create_payment("credit")
print(processor.process())

This approach isolates creation logic, making it easier to add new payment types without modifying the client code that requests them.

Factory Method Pattern

When object creation depends on specific contexts or requires polymorphic behavior, the factory method pattern delegates instantiation to subcalsses. A base class defines an abstract creation interface, and concrete subclasses implement it.

from abc import ABC, abstractmethod

class PaymentProcessor(ABC):
    @abstractmethod
    def create_handler(self):
        pass

    def execute(self):
        handler = self.create_handler()
        handler.process()

class StripeProcessor(PaymentProcessor):
    def create_handler(self):
        return CreditPayment()

class DirectProcessor(PaymentProcessor):
    def create_handler(self):
        return PayPalPayment()

# Usage
gateway = StripeProcessor()
gateway.execute()

This design postpones instantiation decisions to runtime, adhering to the Open/Closed Principle by allowing new processors to be added without altering the base execution logic.

The Singleton Pattern

A singleton restricts a class to a single instance and provides a global access point. This is useful for managing shared resources like configuration managers or database connections.

Implementation via __new__

class ConfigManager:
    _instance = None

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self, db_host, db_port):
        if not hasattr(self, 'initialized'):
            self.db_host = db_host
            self.db_port = db_port
            self.initialized = True

The first call allocates memory and stores the reference in _instance. Subsequent calls return the existing reference. To prevent __init__ from resetting state on repeated instantiations, a guard flag (initialized) ensures initialization runs exactly once.

Exception Handling

Python uses exceptions to manage runtime errors. Instead of crashing, programs can intercept and handle these events gracefully.

Basic Structure

The try block contains code that might fail, while except handles specific error types.

try:
    with open("data.json", "r") as f:
        content = f.read()
except FileNotFoundError as e:
    print(f"File missing: {e}")

Multiple Exceptions and Control Flow

You can catch multiple exception types using a tuple. The else clause executes only if no exceptions occur, and finally runs regardless of the outcome, making it ideal for cleanup operations like closing files or releasing locks.

try:
    value = int("123")
    result = 10 / value
except (ValueError, ZeroDivisionError) as err:
    print(f"Calculation failed: {err}")
else:
    print("Calculation successful")
finally:
    print("Cleanup routine executed")

Custom Exceptions

Applications often require domain-specific error reporting. Custom exceptions inherit from Exception and can carry additional context.

class ValidationError(Exception):
    def __init__(self, field, message):
        self.field = field
        self.message = message
        super().__init__(self.message)

def validate_input(age):
    if age < 0:
        raise ValidationError("age", "Age cannot be negative")

try:
    validate_input(-5)
except ValidationError as ve:
    print(f"Invalid {ve.field}: {ve.message}")

Exception Propagation

If a function does not handle an exception, it propagates up the call stack to the nearest enclosing try block. If it reaches the top level without a handler, the interpreter terminates the program and prints a traceback. Nested try blocks handle inner exceptions first; unhandled ones bubble outward. When an exception is caught and handled, execution continues from the except or finally block rather than resuming at the point of failure.

Modules and Import Mechanics

Python organizes code into modules, which are simply .py files containing definitions and statements.

Import Strategies

Using import module requires prefixing identifiers with the module name, preventing namespace collisions. Alternatively, from module import name injects specific identifiers directly into the local namespace.

import math
print(math.sqrt(16))  # Namespaced access

from os import path
print(path.exists("/tmp"))  # Direct access

Aliasing with as simplifies long names or avoids conflicts:

import pandas as pd
from datetime import datetime as dt

Execution Guard and Controlled Exports

Every module has a __name__ attribute. When executed directly, it equals "__main__"; when imported, it equals the module's filename. This allows modules to include test code that only runs during direct execution.

def calculate(a, b):
    return a + b

if __name__ == "__main__":
    print(calculate(10, 20))  # Only runs when this file is executed

The __all__ list defines the public API of a module. When clients use from module import *, only names in __all__ are imported.

__all__ = ["public_function", "ConstantsClass"]

Packages and Directory Organization

A package is a directory containing multiple modules and an __init__.py file. This file signals Python to treat the directory as a package and controls import behavior.

Package Structure

myapp/
├── __init__.py
├── auth.py
└── utils/
    ├── __init__.py
    └── helpers.py

Submodules are accesed via dot notation: import myapp.utils.helpers. The __init__.py file can remain empty, contain initialization code, or define __all__ to control wildcard imports at the package level. When using from myapp import *, Python imports whatever is listed in myapp/__init__.py's __all__ variable.

Packaging and Distribution

To share code across environments, Python uses distribution packages. The traditional approach relies on a setup.py script.

Setup Configuration

from setuptools import setup, find_packages

setup(
    name="data_tools",
    version="1.2.0",
    description="Utility functions for data processing",
    author="Dev Team",
    packages=find_packages(),
    install_requires=["requests>=2.28.0"]
)

Build and Install Workflow

  1. Build: Compiles the project structure. python setup.py build
  2. Source Distribution: Creates a compressed archive for distribution. python setup.py sdist
  3. Installation: Deploys the package to the Python environment. python setup.py install To specify a custom installation directory, use: python setup.py install --prefix=/custom/path

Modern Python packaging often uses pyproject.toml and build backends like build, but the underlying distribution principles remain consistent: define metadata, bundle source files, and deploy via standard installers.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.