Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to call the original method when it is monkey-patched?

I have a class in the main project I don't want to change.

class A():
    def __init__(self, firstname, lastname):
        self.firstname = firstname
        self.lastname = lastname

    def name(self):
        # this method could be much more complex
        return self.lastname.upper()

I'm trying to build a plugin mechansim. So far so good, I have an extension point like this:

if __name__ == '__main__':
    ''' The main project has an extension point that allows me to do'''
    # for each class extension such as AExtended:
    A.name = AExtended.name
    ''' After the extensions are loaded, some behaviours may be changed'''
    a = A("John", "Doe")
    print(a.name())

A plugin can be written like this:

class AExtended(A):
    ''' This is an extension I provide through a plugin mechanism
    '''
    def name(self):
        return self.firstname + ' ' + self.lastname.upper()

This all works very well. I now get "John DOE".

My problem is that the original name() method can be quite complex. In other words, I can't afford to call self.lastname.upper() in the AExtended. I'd like to call the "super" method, which does not exist any more, because it has been overwritten.

How can I change my code, in order to achieve something like this:

class AExtended(A):
    def name(self):
        # I'm looking for a way to call the base implementation that was in A.name()
        return self.firstname + ' ' + parent.name()

Thanks for your help!

Edit: Some explanations of what I try to do.

  • I want the plugin to patch the behaviour of A. I can't afford to change existing consumers of A
  • There are many classes like A that could be changed, I'd like plugins to have full control and responsibility
  • It's true AExtended does not have to inherit from A, but it was an easy way to access self.firstname. I have no problem following a different design pattern if it can help.

I have a workaround, but it's not very elegant and hard to generalize

class AExtended(A):
    def name(self):
        # I'm looking for a way to call the base implementation that was in A.name()
        return self.firstname + ' ' + self.parentname()
#in main
A.parentname = A.name
A.name = AExtended.name
like image 685
rds Avatar asked Jan 04 '12 11:01

rds


1 Answers

This is what we call a 'decorator' pattern. Replace the original reassignment of name to have it call a function instead, which takes the original. It then returns a new function.

def name_decorator(method):
    def decorate_name(self=None):
        return stuff + method(self)
    return decorate_name
A.name = name_decorator(A.name)

Later, calling A.name will call decorate_name with self as the current instance and method will be available to it which points to the function at the time of the reassignment.

like image 144
Spencer Avatar answered Nov 02 '22 16:11

Spencer