Overriding a class method in python
Context
I'm trying to use some of my "plugins" (I'm not sure if that's the correct definition for this). By "plugin" I mean a module that defines a model (this is scientific code) in such a way that it exists enough to be used elsewhere in the code.
Of course these plugins must follow a pattern that uses some modules / functions / classes defined in my code. Here is a small snippet for the relevant part of my code:
# [In the code]
class AllModels():
def __init__(self):
"""
Init.
"""
self.count = 0
def register(self, name, model):
"""
Adds a model to the code
"""
setattr(self, name, model)
self.count += 1
return
class Model():
def __init__(self, **kwargs):
"""
Some constants that defines a model
"""
self.a = kwargs.get("a", None)
self.b = kwargs.get("b", None)
# and so on...
def function1(self, *args, **kwargs):
"""
A function that all models will have, but which needs:
- to have a default behavior (when the instance is created)
- to be redefinable by the "plugin" (ie. the model)
"""
# default code for the default behavior
return
instance = AllModels()
and here is the relevant "plugin" part:
# [in the plugin file]
from code import Model, instance
newmodel = Model(a="a name", b="some other stuff")
def function1(*args, **kwargs):
"""
Work to do by this model
"""
# some specific model-dependent work
return
instance.register(newmodel)
Additional information and requirements
-
function1
has exactly the same signature for any model plugin, but usually does a different job for each. -
I would prefer the default behavior for
function1
so that if it's not defined by the plugin, I can still do something (try different possibilities and / or raise a warning / error). -
The plugin
function1
may use some other functions that are only defined in this plugin. I am arguing this because the code works with the multiprocessing module and I need an instanceinstance
AllModels
to be able to be calledfunction1
in child processes.instance
is defined in the parent process as well as in model plugins, but will be used in different child processes (however no changes have been made to it). -
it would be great if
function1
, when "overridden" by a plugin, would be able to access the attributes of the instanceModel
(ieself
).
Problem
I've read many different sources of python documentation and some SO questions. I only see two / three possible solutions to this problem:
1) without declaring a method function1
in the class Model
, but simply setting it as an attribute when the plugin creates a new instance.
# [in the plugin file]
def function1(*args, **kwargs):
# ....
return
newmodel.function1 = function1
and then call it when needed. In this case, the attribute function1
on the object Model
will probably start with None
. One of the caveats is that function1
there is no βdefault behaviorβ for (it needs to be handled in code, for example, when testing if instance.function1 is None: ...
), and even more - I cannot access self
this way ...
2) using python decorators somehow. I've never used this, and the documentation I've read is not that straightforward (I mean not straightforward due to the sheer number of uses it has). But this seems to be a good solution. However, I am worried about its performance impact (I read that it can slow down the execution of the decorated function / method). If this solution is the best option, then I would like to know how to use it (maybe a quick snippet) and if class attributes can be used Model
:
# [in the plugin file]
@mydecorator
def function1(self, *args, **kwargs):
"""
I'm not sure I can use *self*, but it would be great since some attributes of self are used for some other function similar to *function1*...
"""
# some stuff using *self*, eg.:
x = self.var **2 + 3.4
# where self.var has been defined before, eg.: newmodel.var = 100.
3) using a module types
and its MethodType
... I'm not sure if this is relevant in my case ... but I could be wrong.
As you will probably see after this long question, I am not very good at such python functions and now my understanding of decorators is really awkward. While I was reading some documentation, I thought it might be worth asking a question here, since I'm not sure what direction to take to address my problem.
Decision
The beauty of Sender's answer is that it is really simple and obvious ... and skipping that is a shame. Sorry for contaminating SO with this question.
source to share
Well, if I'm wrong, you want to subclass Model
. This is similar to creating an instance Model
and replacing its attribute function1
with a function defined in the plugin module (i.e. your option 1); but it is much cleaner and takes care of all the details for you:
# [in the plugin file]
from code import Model, instance
class MyModel(Model):
def function1(*args, **kwargs):
"""
Work to do by this model
"""
# some specific model-dependent work
return
newmodel = MyModel(a="a name", b="some other stuff")
instance.register(newmodel)
Thus, all other methods (functions "attached" to the instance Model
) inherit from Model
; they will behave exactly the same, but function1
will be overridden and follow your customized definition function1
.
source to share
What you are trying to do can be done in fairly simple ways - just by using methods with an object-oriented interface and taking advantage of what Python functions are also normal objects -
One simple thing is to simply have your own "model" class to take "function1" as a parameter and store it as a member of the object.
Some code like this, with minimal changes to your code - although much more interesting things are certainly possible:
# [In the code]
class AllModels():
def __init__(self):
"""
Init.
"""
self.count = 0
def register(self, name, **kwargs):
"""
Adds a model to the code
"""
model = Model(**kwargs)
setattr(self, name, model)
self.count += 1
return
class Model():
def __init__(self, **kwargs):
"""
Some constants that defines a model
"""
self.a = kwargs.get("a", None)
self.b = kwargs.get("b", None)
if "function1" in kwargs:
self.real_function1 = kwargs["function1"]
self.function1.__doc__ = kwargs["function1"].__doc__
# and so on...
def function1(self, *args, **kwargs):
"""
A function that all models will have, but which needs:
- to have a default behavior (when the instance is created)
- to be redefinable by the "plugin" (ie. the model)
"""
if self.real_function1:
return self.real_function1(self, *args, **kwargs)
# default code for the default behavior
return
instance = AllModels()
and
# [in the plugin file]
from code import Model, instance
newmodel = Model(a="a name", b="some other stuff")
def function1(self, *args, **kwargs):
"""
Work to do by this model
"""
# some specific model-dependent work
return
instance.register("name", function1 = function1, a="a name", b="some other stuff")
source to share
Could you write a dummy function1()
function in the class Model
and raise
a NotImplementedError
? Thus, if anyone tries to inherit from Model
without implementation function1()
, they will get an exception when they try to run the code. If you use code for them, you can catch this error and return an error message to the user.
For example:
class Model:
#Your code
def function1():
raise NotImplementedError("You need to implement function1
when you inherit from Model")
Then, when you run the code, you can do the following:
try:
modelObj.function1()
except NotImplementedError as e:
#Perform error handling here
EDIT: The official Python documentation for NotImplementedError says: "In custom base classes, abstract methods must throw this exception when they require a method to be overridden. This seems to fit the requirements here.
source to share