2

I want to create a subclass of a class of an existing package (whose source code I don't want to/cannot change). The objects of the class are initialized just using a string and then populated later on using all kind of add functions. A minimal example could look like this (without any add functions):

import copy


class Origin(object):
    def __init__(self, name):
        self.name = name
        self.dummy_list = [1, 2, 'a']
        self.dummy_stuff = {'a': [12, 'yt']}

    def make_copy(self):
        return copy.deepcopy(self)

    def dummy_function(self):
        return len(self.dummy_list)

I want to create a subclass in such a way that I can initialize its instances using an instance of Origin. A straightforward way would be

class BasedOnOrigin(Origin):
    def __init__(self, origin_instance, new_prop):
        Origin.__init__(self, origin_instance.name)
        self.dummy_list = copy.deepcopy(origin_instance.dummy_list)
        self.dummy_stuff = copy.deepcopy(origin_instance.dummy_stuff)
        self.new_prop = new_prop

The annoying thing there is, that I need to copy all kind of things which I need to know about in advance.

Another option would be

class BasedOnOrigin2(Origin):
    def __init__(self, origin_instance, new_prop):
        Origin.__init__(self, origin_instance.name)
        self = origin_instance.make_copy()
        self.new_prop = new_prop

but the self = part looks rather non-standard and new_prop is not set, so I would need an extra function for this.

Is there a standard way of doing this?

An alternative to the above would be to add the additional functions to existing instances using e.g.

from functools import partial

def add_function(obj, func):

    setattr(obj, func.__name__, partial(func, obj))

but this can be annoying if there are (i) a lot of functions to add and (ii) a lot of instances to which one wants to add functions.

Cleb
  • 25,102
  • 20
  • 116
  • 151
  • Can you tell more about your use case? (the problem you are trying to solve with this solution) – bruno desthuilliers Jun 26 '18 at 15:36
  • @brunodesthuilliers: The problem I want to solve is the initialization of the subclass instances; I want to have an easy way to initialize these instances using an instance of the parent class. As I can initialize instances of the parent class only with a string, I am wondering about the best way to achieve that. – Cleb Jun 26 '18 at 21:36
  • I'm afraid you didn't understand my question... What you describe in your OP and repeat in this comment is about what you think is the solution to a problem you have and have not explained. What I'm asking you is what is the REAL problem you want to solve with this solution. IOW: **why do you want to initialize a subclass instance from an instance of it's parent class ?**. There might (or not..) be better (or just canonical) solutions to your real problem, but we can't tell without knowing about the real problem. We very probably have a `XY problem` here... – bruno desthuilliers Jun 27 '18 at 09:09

2 Answers2

2

but the self = part looks rather non-standard and new_prop is not set

self is just a plain local variable, so rebinding it only effects the local scope indeed.

Is there a standard way of doing this?

From what you describe it looks like your real problem is that you have instances of class created by another lib that you don't want / cannot modify and what you really want is to add new methods (and eventually override some methods) to those objects, but cannot since you can tell this lib to use your own class instead.

If the point is purely and simply "replace" the original class with your own version of it (so all instances of the original class are impacted by the change), the canonical solution is to monkeypatch the original class:

from otherlib import TheClass

def patch_the_class():
    # we do this in a function to avoid
    # polluting the global namespace

    # add a new method

    def newmethod(self):
       # code here

    TheClass.newmethod = newmethod

    # override an existing method

    # keep a reference to the original so
    # we can still use it:
    _original = TheClass.some_method

    def mymethod(self, arg):
        something = _original(self, arg)
        # additional stuff here
        return something

    TheClass.some_method = mymethod

patch_the_class()           

Just make sure this is executed before any use of the patched class and you're done.

The pro of this solution (wrt/ patching each instance individually) is a lesser cost and the assurance that no one will ever forget to patch an instance.

Now note that monkeypatches are to be considered as either a temporary workaround or a last-resort hack. If the lib you are patching is OSS, you can modify it to either improve the original class or implement some way to make the concrete class to use configurable and contribute it back.

bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
  • Patching it is then :) And probably indeed better to patch the class rather than the instances as I did with the `add_method` – Cleb Jun 27 '18 at 12:16
0

I think the best approach is defining a function that will extend original origin instance without copying it e.g.

def exdend(*origin_instances):
    def my_function_one(self):
        pass
    def my_function_two(self):
        pass

    for origin_instance in origin_instances:
        setattr(origin_instance, my_function_one.__name__, partial(my_function_one, origin_instance))
        setattr(origin_instance, my_function_two.__name__, partial(my_function_two, origin_instance))

    return origin_instances
donnyyy
  • 452
  • 3
  • 11
  • Yes, that's basically the `add_function` idea. Just wondering whether there is something smarter than this... :) – Cleb Jun 26 '18 at 13:34
  • It is pretty good solution because of avoiding object deep copy and the only problem is pretty similar block of ``setattr`` lines. – donnyyy Jun 26 '18 at 13:38