Skip to main content

A basic framework for implementing an extension pattern

Project description

objectextensions

A basic framework for implementing an extension pattern

Summary

The point of this framework is to provide a more modular alternative to object inheritance.

Consider the following use case: You have an abstract class Car intended to represent a generic real-world car, and need a pattern that allows you to optionally add more features to it. For example, you may want to add a convertible roof or a touchscreen on the dashboard, but these features will not necessarily be added to every subclass of Car you create.

Applying standard OOP here means you would need to make a subclass every time a new combination of these optional features is needed. In the above case, you may need one subclass for a car with a convertible roof, one subclass for a car with a touchscreen, and one that has both features. As the amount of optional features increases, the amount of possible combinations skyrockets. This is not a scalable solution to the problem.

objectextensions provides an elegant way to handle scenarios such as this one. Rather than creating a new subclass for each possible combination, you create one extension representing each feature. When you need to create an instance of a car with a particular set of features, take the parent class and pass it the exact set of extensions you want to apply via the .with_extensions() method.

Note that this pattern is intended to be used alongside inheritance, not to replace it entirely. The two can be mixed without issue, such that (for example) a subclass could extend a parent class that has pre-applied extensions like so:

class SpecificCarModel(Car.with_extensions(TouchscreenDash)):
    pass

Quickstart

Setup

Below is an example of an extendable class, and an example extension that can be applied to it.

from objectextensions import Extendable


class HashList(Extendable):
    """
    A basic example class with some data and methods.
    Inheriting Extendable allows this class to be modified with extensions
    """

    def __init__(self, iterable=()):
        super().__init__()

        self.values = {}
        self.list = []

        for value in iterable:
            self.append(value)

    def append(self, item):
        self.list.append(item)
        self.values[item] = self.values.get(item, []) + [len(self.list) - 1]

    def index(self, item):
        """
        Returns all indexes containing the specified item.
        Much lower time complexity than in a typical list due to dict lookup usage
        """

        if item not in self.values:
            raise ValueError(f"{item} is not in hashlist")

        return self.values[item]
from objectextensions import Extension


class Listener(Extension):
    """
    This extension class is written to apply a counter to the HashList class,
    which increments any time .append() is called
    """

    @staticmethod
    def can_extend(target_cls):
        return issubclass(target_cls, HashList)

    @staticmethod
    def extend(target_cls):
        Extension._set(target_cls, "increment_append_count", Listener.__increment_append_count)

        Extension._wrap(target_cls, "__init__", Listener.__wrap_init)
        Extension._wrap(target_cls, 'append', Listener.__wrap_append)

    def __wrap_init(self, *args, **kwargs):
        Extension._set(self, "append_count", 0)
        yield

    def __wrap_append(self, *args, **kwargs):
        yield
        self.increment_append_count()

    def __increment_append_count(self):
        self.append_count += 1

Instantiation

HashListWithListeners = HashList.with_extensions(Listener)
my_hashlist = HashListWithListeners(iterable=[5,2,4])

or, for shorthand:

my_hashlist = HashList.with_extensions(Listener)(iterable=[5,2,4])

Result

>>> my_hashlist.append_count  # Attribute that was added by the Listener extension
3
>>> my_hashlist.append(7)  # Listener has wrapped this method with logic which increments `.append_count`
>>> my_hashlist.append_count
4

Additional Info

  • As extensions do not currently invoke name mangling, adding private members (names which begin with double underscores) to Extendable classes via extensions is not recommended; doing so may lead to unintended behaviour. Using protected members (names with a single leading underscore) instead is encouraged, as name mangling does not come into play in this case.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

objectextensions-3.0.0.tar.gz (6.4 kB view details)

Uploaded Source

File details

Details for the file objectextensions-3.0.0.tar.gz.

File metadata

  • Download URL: objectextensions-3.0.0.tar.gz
  • Upload date:
  • Size: 6.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.12.0

File hashes

Hashes for objectextensions-3.0.0.tar.gz
Algorithm Hash digest
SHA256 524ca1ff25b661c339e7f400806f1d6246e3d0ebc1de9ac2151aadef323be03b
MD5 46949cb3e8dfbcabfb3a29fac0f293af
BLAKE2b-256 4c7faa2207db30a6eb7a1f782d02c680a68ec62dcd751be816af9452093f5f0e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page