0

Background:

The project involves creating a "Pipeline" which takes in various objects whose methods and properties are used to drive an analysis. Those objects should inherit from an abstract class defining abstract methods and properties. The intention from this is that the steps in the pipeline may be altered by new developers by overriding an existing class' methods whilst not needing to entirely understand the entire inner workings of the pipeline. Different pipelines may also be constructed using these classes in the future.

Problem:

Because different steps of this pipeline may require different input arguments (e.g. if one wants to change the definition of an evaluation, that evaluation may want additional arguments and may want those at runtime as opposed to being hardcoded into the class). An ideal situation would be that a programmer may inherit from an abstract class, redefine the signature to contain additional arguments, which are injected at runtime via a config, whilst the Pipeline still calls these methods with the original signatures. This would effectively give a highly flexible framework to build on as a developer as you can write whatever signatures you want as long as you define a config when running the script to handle them, whilst also meaning the pipeline never needs to be rewritten.

My solution:

My approach has been to basically build a class factory which builds classes at runtime. The idea is to take the class defined by the user, rip it apart and get all its data, replace every method with a partialmethod and return a new class from this new information.

Code:

class PartialClassFactory:
    """ Acts as a decorator so that the user can define what needs to be partialled at runtime."""
    def __init__(self, *method_attributes):
        self.method_attributes = method_attributes

    def __call__(self, cls):
        cls._update_attrs = self.method_attributes
        return cls

    def get_partial_cls(self, cls, config):
        partial_cls_attrs = dict(cls.__dict__)
        partial_methods = {}
        for method, attributes in cls._update_attributes:
            new_values = {
                attribute: config[attribute]
                for attribute in attributes
                if attribute in config.keys()
            }
            partial_methods[method] = partialmethod(cls.__dict__[method], **new_values)

        partial_cls_attrs = partial_class_attrs.update(partial_methods)
        partial_class_attrs.pop('__dict__')
        partial_class_attrs.pop('__weakref__')
        metaclass = type(cls)
        return metaclass(f"Configured{cls.__name__}", cls.__bases__, partial_class_attrs)

        return cls

@PartialClassFactory(('__init__', ('attr_to_replace')), ('method_a': ('attr_to_replace_')))
class ToBePartialled:
    """Not a real class, just demonstrating how the decorator would be used"""
    def __init__(self, attr_to_replace):
        self.attr_to_replace = attr_to_replace

    def method_a(attr_to_replace_):
        return attr_to_replace_ 

# Creating the new class would go something like
config = {'attr_to_replace': 1, 'attr_to_replace_': 2}
cls_factory = PartialClassFactory()
ConfiguredToBePartialled = cls_factory.get_partial_cls(ToBePartialled, config)

Questions: - This feels hacky, is there a better solution to the problem that I've missed? - Could there be issues with this solution? Is there any data from the original class that won't be retained that should be? - Are there any cleaner ways to implement the above solution?

Alesi Rowland
  • 379
  • 2
  • 16
  • Your use case might benefit from [metaclasses](https://docs.python.org/3/reference/datamodel.html#metaclasses) – Yasin Bahtiyar Jun 01 '20 at 10:34
  • When I tried using a metaclass, I couldn't inject the config at runtime as the metaclass built the class on import - can you elaborate how you would do this? @YasinBahtiyar – Alesi Rowland Jun 01 '20 at 10:37
  • https://stackoverflow.com/questions/45536595/understanding-call-with-metaclasses maybe this helps? – Yasin Bahtiyar Jun 01 '20 at 10:54

0 Answers0