Skip to content

Instantly share code, notes, and snippets.

@nmfzone
Last active February 4, 2020 03:43
Show Gist options
  • Select an option

  • Save nmfzone/9345fe26bb43db65bd5c2d966c1c86d2 to your computer and use it in GitHub Desktop.

Select an option

Save nmfzone/9345fe26bb43db65bd5c2d966c1c86d2 to your computer and use it in GitHub Desktop.
Monkey Patch: Override method without extending class in Python (e.g. Patch method, Override core method)
def override_method(cls, after=True):
def decorator(func):
new_parent_method_name = '_' + func.__name__ + '_parent'
setattr(cls, new_parent_method_name, getattr(cls, func.__name__))
@wraps(func)
def wrapper(self, *args, **kwargs):
if after:
parent_result = getattr(self, new_parent_method_name)(*args, **kwargs)
return func(self, parent_result, *args, **kwargs)
elif after == False:
func(self, *args, **kwargs)
return getattr(self, new_parent_method_name)(*args, **kwargs)
return func(self, *args, **kwargs)
setattr(cls, func.__name__, wrapper)
return func
return decorator
@nmfzone

nmfzone commented Jan 7, 2020

Copy link
Copy Markdown
Author

When you override method in a class, you may write something like these:

class BaseClass():
    def foo(x, y):
        return x + y

class ClassOne(BaseClass):
    def foo(x, y):
        res = super().foo(x, y)  # call parent method before your manipulation
        return res * 2

class ClassTwo(BaseClass):
    def foo(x, y):
        x = x - 2
        return super().foo(x, y)  # call parent method after your manipulation

class ClassThree(BaseClass):
    def foo(x, y):
        return ((x - 2) + y) * 2  # not call parent method at all

@nmfzone

nmfzone commented Jan 7, 2020 •

Copy link
Copy Markdown
Author

It's fine (actually the correct way) when you only need to override method in that class.

But, what about when the BaseClass is a core class? Then it's gets used everywhere, and has other core class that override it?

This is useful when you only want to modify / override the method in the root class.

@override_method(BaseClass)  # just like ClassOne does
def foo(self, parent_result, x, y):  # you had to define `parent_result` argument, it's the parent result return value.
    return parent_result * 2

@override_method(BaseClass, False)  # just like ClassTwo does
def foo(self, x, y):  # you should not define `parent_result` argument, it's just used when `after=True`
    x = x - 2  # this is just for example. It will not working as expected, since it's local variable.

@override_method(BaseClass, None)  # just like ClassThree does
def foo(self, x, y):  # you should not define `parent_result` argument, it's just used when `after=True`
    return ((x - 2) + y) * 2

You just need to place that code somewhere, the place that your app always access it no matter what, for example in __init__.py file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment