1. The basic idea: use an extra class to hold the methods
I found a meaningful way to do the work:
First, we define such a BaseClass:
class MethodPatcher:
@classmethod
def patch(cls, target):
for k in cls.__dict__:
obj = getattr(cls, k)
if not k.startswith('_') and callable(obj):
setattr(target, k, obj)
Now that we have an original class:
class MyClass(object):
def a(self):
print('a')
Then we define the new method which we want to add on a new Patcher
class:
(Do not make the method name starts with an _
in this case)
class MyPatcher(MethodPatcher):
def b(self):
print('b')
Then call:
MyPatcher.patch(MyClass)
So, you'll find the new method b(self)
is added to the original MyClass
:
obj = MyClass()
obj.a() # which prints an 'a'
obj.b() # which prints a 'b'
2. Make the syntax less verbose, we use class decorator
Now if we have the MethodPatcher
decalred, we need to do two things:
- define a child class
ChildClass
of ModelPatcher
which contains the extra methods to add
- call
ChildClass.patch(TargetClass)
So we soon found that the second step can be simplified by using a decorator:
We define a decorator:
def patch_methods(model_class):
def do_patch(cls):
cls.patch(model_class)
return do_patch
And we can use it like:
@patch_methods(MyClass)
class MyClassPatcher(MethodPatcher):
def extra_method_a(self):
print('a', self)
@classmethod
def extra_class_method_b(cls):
print('c', cls)
# !!ATTENTION!! the effect on declaring staticmethod here may not work as expected:
# calling this method on an instance will take the self into the first argument.
# @staticmethod
# def extra_static_method_c():
# print('c')
3. Wrap together
So, we can now put the definition of MethodPatcher
and patch_method
into a single module:
# method_patcher.py
class MethodPatcher:
@classmethod
def patch(cls, target):
for k in cls.__dict__:
obj = getattr(cls, k)
if not k.startswith('_') and callable(obj):
setattr(target, k, obj)
def patch_methods(model_class):
def do_patch(cls):
cls.patch(model_class)
return do_patch
So we can use it freely:
from method_patcher import ModelPatcher, patch_model
4. Final solution: More simple declaration
Soon I found that the MethodPatcher
class is not nessesary, while the @patch_method
decorator can do the work, so FINALLY we only need a patch_method
:
def patch_methods(model_class):
def do_patch(cls):
for k in cls.__dict__:
obj = getattr(cls, k)
if not k.startswith('_') and callable(obj):
setattr(model_class, k, obj)
return do_patch
And the usage becomes:
@patch_methods(MyClass)
class MyClassPatcher:
def extra_method_a(self):
print('a', self)
@classmethod
def extra_class_method_b(cls):
print('c', cls)
# !!ATTENTION!! the effect on declaring staticmethod here may not work as expected:
# calling this method on an instance will take the self into the first argument.
# @staticmethod
# def extra_static_method_c():
# print('c')
__init__()
anywhere, so it's not a big surprise it's not getting executed. Moreover, you would have to add the new method on the class, not on the instance. – Morda.func = staticmethod(a._func)
. – Mord