Monkey patching? Proxy objects? Getting rid of a factory method and recycling a generic object

91 views Asked by At

Basically, I have a generic class with a lot of methods. Before accessing those methods, I have to check its 'type' field, for example:

Note: generic_class_t is from a 3rd-party library I cannot control or re-design. I can only wrap but I want to be performant.

Here's the problem:


class generic_class_t:
    def __init__(self):
        self.type = ...

    def method1(self):
        pass
    def method2(self):
        pass
    attr1 = property(lambda self: ...)
    attr1 = property(lambda self: ...)

User code working with the generic class, would always have to use something like this:

if cls.type == 1:
    cls.method1()
    cls.attr1
elif cls.type == 2:
    cls.method2()
    cls.attr2
...

What I want to do, is wrap that class in specialized classes:

class cls1:
    """Wrapper for type=1"""
    def __init__(self, cls):
        self.obj = cls
    def method1():
        self.obj.method1()

    attr = property(lambda self: cls.attr1)

    # There are no method2, etc.

class cls2:
    """Wrapper for type=1"""
    def __init__(self, cls):
        self.obj = cls
    def method2():
        self.obj.method2()

    attr = property(lambda self: cls.attr2)

    # There are no method2, etc.

Now of course, I would have to have a factory method:

o1 = wrap(cls) or: o2 = wrap(cls)

def wrap(cls):
    if cls.type == 1:
        return cls1(cls)
    elif cls.type == 2:
        return cls2(cls)

The problem with that is memory and performance of the factory.

For each generic class instance, another specialized object would have to be constructed.

My question is:

Is there is a way to directly / quickly patch the existing generic class instance and super-impose the specialized cls1 and cls2, etc.?

I prefer not to create a new object at all.

I know a proxy object method can come in handy, but again, we have the proxy object itself.

Can I patch the __dict__ / swap the __dict__ , etc. and do something fast to de-generalize the generic class and have specialized classes hijack its method?

I hope this is clear.

I appreciate any advise.

Thanks!

1

There are 1 answers

0
Mad Physicist On

I think you're underestimating the power of dictionaries. For example, let's say your generic interface has a method do and a propery attr. Your factory does not need a giant if statement to choose which implementation it needs:

class Wrapper:
    def __init__(self, obj):
        self.obj = obj

class Type1(Wrapper):
    def do(self):
        return self.obj.method1()

    @property
    def attr(self):
        return self.obj.attr1

class Type2(Wrapper):
    def do(self):
        return self.obj.method2()

    @property
    def attr(self):
        return self.obj.attr2

type_map = {
    1: Type1,
    2: Type2
}

def type_factory(obj):
    return type_map(obj.type)(obj)

This way allows you to perform arbitrary operations on the underlying object. For example, with a couple of tweaks, you can have a version that does something like this:

class TypeCombined(Wrapper):
    def do(self):
        return self.obj.method1() + self.obj.method2()

    @property
    def attr(self):
        return self.obj.attr1 + self.obj.attr2

def type_factory(obj, type=None):
    return type_map[obj.type if type is None else type](obj)

If you need something simpler because each type simply corresponds to a selection of methods and attributes, you can generate the classes with something like a metaclass, or more simply, use a configurable base class:

type_map = {
    1: ('method1', 'attr1'),
    2: ('method2', 'attr2')
}

class Wrapper:
    def __init__(self, obj):
        self.obj = obj
        self.method_name, self.attr_name = type_map[obj.type]

    def do(self):
        return getattr(self.obj, self.method_name)()

    @property
    def attr(self):
        return getattr(self.obj, self.attr_name)