Description
My question is very similar to the question raised here, with one difference: I do not want to directly edit or replace anything in the original class instance.
So, basically, imagine that we have a class defined as
class Class1(object):
"""
This is Class1.
"""
def __init__(self, name):
self.name = name
def a(self):
"Method a"
return("This is the Class1 a()-method!")
def b(self):
"Method b"
return("This is the Class1 b()-method!")
def bb(self):
"Method bb"
return(self.b())
Now what I want to do is create a function that the user can call, where it provides me with an instance of Class1
(or any derived subclass).
Then, I return an object that behaves exactly the same as the provided instance, except that the b()
-method has been replaced with
def b(self):
return('This is the new Class1 b()-method!')
Everything else about this object must be exactly the same as it was in the provided instance, such that the new object can be used in any place the old one could.
It is basically as if the definition of Class1
(or any used subclass) already used this definition of b()
to begin with.
Attempts
So, I have already tried a few things, but every one of them has at least one problem that I do not like.
Note that I left out any checks to see if a provided object is indeed an instance of the Class1
class or any derived subclasses, as it adds nothing to my descriptions.
I tried using the solution that was given here, which gave me the following:
from types import MethodType
# Define handy decorator for overriding instance methods
def override_method(instance):
def do_override(method):
setattr(instance, method.__name__, MethodType(method, instance))
return(do_override)
# Define function that returns new Class1 object
def get_new_Class1_obj(class1_obj):
# Override b-method
@override_method(class1_obj)
def b(self):
return('This is the new Class1 b()-method!')
# Return it
return(class1_obj)
This solution basically does everything I want, except that it directly replaces the b()
-method in the provided class1_obj
with the new definition.
Therefore, once the get_new_Class1_obj()
-function has been called, the original class1_obj
can no longer be used in its original state.
This is a problem, as for the application I have, I actually require the original b()
-method to still be available (I simply did not use such a thing in this example to keep it a bit simple).
Another way I tried doing this involved using a class factory (I have tried several different versions of this, with below probably the closest I have to getting what I want):
# Define function that returns new Class1 object
def get_new_Class1_obj(class1_obj):
# Define list with all methods that are overridden
override_attrs = ['__init__', '__getattribute__', '__dir__', 'b']
# Make a subclass of the class used for class1_obj
# This is required for functions like 'isinstance'
class new_Class1(class1_obj.__class__, object):
# Empty __init__ as no initialization is required
def __init__(self):
pass
# Make sure only overridden attributes are used from this class
def __getattribute__(self, name):
if name in override_attrs:
return(super().__getattribute__(name))
else:
return(getattr(class1_obj, name))
# Use the __dir__ of class1_obj
def __dir__(self):
return(dir(class1_obj))
# Override b-method
def b(self):
return("This is the new Class1 b()-method!")
# Initialize new_Class1
new_class1_obj = new_Class1()
# Return it
return(new_class1_obj)
This is also very close to what I want (even though it is annoying to keep updating the override_attrs
list) and I am now able to use the original class1_obj
within the new_class1_obj
if I want.
However, the problem here is that the bb()
-method of the new_class1_obj
will not work properly, as it will use the b()
-method of class1_obj
and not the one of new_class1_obj
.
As far as I know, there is no way to enforce this without knowing that the method bb()
exists in a form like this.
As it would be possible that somebody subclasses Class1
and introduces a c()
-method that calls b()
, this solution would fail to work properly (while that would work properly with the first solution).
Not subclassing Class1
here would get rid of some annoyances, but it would also mean that functions such as isinstance
do not work properly anymore, while not fixing the problem with the bb()
-method.
Solution?
So, I currently cannot come up with a solution for this (I have been trying for a few days now).
I am considering using my first attempt solution, but instead of immediately replacing the b()
-method, I first assign b()
to something like _old_b()
or _b()
(obviously making sure it does not exist already) and then replace b()
.
I do not really like that solution, as it still feels way too hacky and dirty to me.
So, does anybody have an idea for this? In my mind, it sounds like a very simple problem: I have an instance and I want to update one of its instance methods with a new one, without modifying the original instance. But, it seems that is not so simple after all.
Example
A full example use of this would be:
# Define original Class1 class
class Class1(object):
"""
This is Class1.
"""
def __init__(self, name):
self.name = name
def a(self):
"Method a"
return("This is the Class1 a()-method!")
def b(self):
"Method b"
return("This is the Class1 b()-method!")
def bb(self):
"Method bb"
return(self.b())
# Define new b()-method
def b(self):
# Return both old and new b() output
return(class1_obj.b(), "This is the new Class1 b()-method!")
# Define function that returns new Class1 object
def get_new_Class1_obj(class1_obj):
<code involving getting new_class1_obj>
# Use with expected outputs
>>> class1_obj = Class1('TEST')
>>> new_class1_obj = get_new_Class1_obj(class1_obj)
>>> class1_obj is new_class1_obj
False
>>> class1_obj.name
'TEST'
>>> class1_obj.a()
"This is the Class1 a()-method!"
>>> class1_obj.b()
"This is the Class1 b()-method!"
>>> class1_obj.bb()
"This is the Class1 b()-method!"
>>> new_class1_obj.name
'TEST'
>>> new_class1_obj.a()
"This is the Class1 a()-method!"
>>> new_class1_obj.b()
("This is the Class1 b()-method!", "This is the new Class1 b()-method!")
>>> new_class1_obj.bb()
("This is the Class1 b()-method!", "This is the new Class1 b()-method!")
>>> class1_obj.name = 'TEST2'
>>> class1_obj.name
'TEST2'
>>> new_class1_obj.name
'TEST2'