Search code examples
pythonooppython-3.xmonkeypatchingoverriding

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

Solution

  • Here's a full example of what I was hinting at. Feel free to yell at me and have me merge my answers, or downvote one or whatever, just easier to provide an alternative as a new answer. I'll let the code do the talking instead of poorly explaining it. :)

    ## Some shared class that is used all over the place and needs to be patched.
    
    class A(object):
        def __init__(self):
            self.firstname = 'Bob'
    
        # Print my first name.
        def name(self):
            return self.firstname
    
        # Use this to allow patching arbitrary methods...
        @classmethod
        def patch(cls, func_name):
            def patch_by_name(new_func):
                old_func = getattr(cls, func_name)
                def patched_func(self):
                    return new_func(self, old_func)
                setattr(cls, func_name, patched_func)
            return patch_by_name
    
    ## Some other area of the code where you want to throw in a patch
    
    class PatchedA(A):  # doesn't need to subclass, but comes in handy sometimes
        @A.patch('name')
        def name(self, orig_func):
            return 'I am ' + orig_func(self) + 'McWizwaz'
    
    print 'Who are you, A class?'
    print A().name()  # prints 'I am Bob McWizwaz'