Skip to main content

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.

Metadata

Release files for objectextensions 3.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for objectextensions 3.0.0
File Size Uploaded Provenance Actions
objectextensions-3.0.0.tar.gz 6.4 kB Without provenance Details for objectextensions-3.0.0.tar.gz

Release files / objectextensions-3.0.0.tar.gz

Download URL objectextensions-3.0.0.tar.gz
Size 6.4 kB
Tags Source
SHA-256 checksum
How to use checksums
524ca1ff25b661c339e7f400806f1d6246e3d0ebc1de9ac2151aadef323be03b
BLAKE2b-256 checksum
How to use checksums
4c7faa2207db30a6eb7a1f782d02c680a68ec62dcd751be816af9452093f5f0e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.2 CPython/3.12.0

Security for objectextensions 3.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page