13

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
rds
  • 26,253
  • 19
  • 107
  • 134
  • Yes, I read how to [call the parent class method](http://stackoverflow.com/questions/805066/how-to-call-a-parent-classs-method-from-child-class-in-python) but in `AExtended.name`, `self` is a `A` because of the overriding – rds Jan 04 '12 at 11:42
  • I might be more lcuky with [overriding a private method](http://stackoverflow.com/questions/2484215/how-do-i-override-a-parent-classs-functions-in-python) but I cannot change the A method name. – rds Jan 04 '12 at 11:47
  • Given your clarifications, I would suggest you change the question title. A more accurate title might be "How to call the original method when it is monkey-patched?" – Weeble Jan 04 '12 at 13:17
  • @Weeble done. I did not know this was called monkey-patching – rds Jan 04 '12 at 14:27
  • No problem, I just want you to get the best answers possible. – Weeble Jan 04 '12 at 15:57

5 Answers5

22

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.

Spencer
  • 665
  • 3
  • 8
  • +1 because python decorators are doing exactly what I want. I'll accept this answer when I can check it can be generalized. – rds Jan 04 '12 at 14:20
  • I use Python 3. What syntax sugar can help? – rds Jan 04 '12 at 14:21
  • See: http://docs.python.org/reference/compound%5Fstmts.html#function - rather than do explicit overrides, you can place `@my_decorator` right before any function def you want to decorate. – Spencer Jan 04 '12 at 14:26
  • Lemme phrase that better... `A` would define functions, along with a foo method whose sole purpose is to do what I originally pasted. It takes a function and returns a closure. Later, someone can subclass it and put @foo before their `name()` impl, which at runtime causes the patch/punch to happen. Direct consumers of `A` get the subclass' version w/ out having to touch all that code. – Spencer Jan 04 '12 at 14:36
  • Decorators syntax exist since python 2.4. – Ski Jan 04 '12 at 16:20
  • Yup. Wanted to give a working example. People out there still run 2.2! – Spencer Jan 04 '12 at 16:37
  • Still not sure how to write this as a `@decorator`, as the (inner) function definition seems to happen in the current local scope, before I can bind `method` to the old class method. Any hints on how to produce a working example? – Juan A. Navarro Jul 24 '14 at 14:05
6

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'
Spencer
  • 665
  • 3
  • 8
  • (And of course, you're free to make it not a classmethod and just a standalone function and call it as `@patch(A, 'name')` instead, thus making it usable for any class anywhere.) – Spencer Jan 04 '12 at 16:46
  • Wouldn't it be easier to do put that `@patch` decorator outside of `A` and also apply it to the patching `def name` _outside_ any subclass? What exactly is the purpose of a subclass `PatchedA` anyway, when you replace the original `A`'s method? – Tobias Kienzler Aug 28 '13 at 07:15
2
class ABase(object):
    def name(self):
        pass

class A(object):
    pass

class AExtension(ABase):
    def name(self):
        return ABase.name(self)

A.name = AExtension.name
Ski
  • 14,197
  • 3
  • 54
  • 64
  • I'm not sure I understand what class should be used. I need to copy all methods from `ABase` to `A` (`A.name=ABase.name` etc) and then load extensions (`A.name=AExtension.name`)? – rds Jan 04 '12 at 13:16
  • `A` already has all methods of `ABase` because `A` extends `ABase`. But overall the idea of reassigning methods to other class looks suspicious. You might find some ideas how to correctly a minimal plugin architecture in this topic: http://stackoverflow.com/questions/932069/building-a-minimal-plugin-architecture-in-python – Ski Jan 04 '12 at 13:28
  • @Skirmantas, it's not uncommon to decorate functions like he wants to do. Newer versions of the language even support syntactic sugar to make my example even easier to write. – Spencer Jan 04 '12 at 13:48
  • @Skirmantas Thanks for the pointer (already opened in the past :-). I actually build my plugin mechanism from Mary Alchin's simple plugin framework. I have already a couple of extensions point at strategic places. However, I think it would be overkill to place extension points everywhere (particularly in the model). That's why I'd rather let plugins overwrites some parts if they want to. – rds Jan 04 '12 at 14:18
  • You don't even need to put the patching `def name` in any (sub)class, do you? – Tobias Kienzler Aug 28 '13 at 07:17
1

Standing on the shoulders of giants (@spencer), here's a bit more generic sample. It lets one patch a class Original without touching its source code. In this version, parameters fly all over the place, and a reference to an arbitrary context is given to the new method.

class Original:
    def f( self, a, b = None ):
        print( "f", a, b )
        self.g( b )

    def g( self, b ):
        print( "g", b )

class Patcher:
    def prepare_class( self, clazz ):
        @classmethod
        def on_class_patcher( cls, func_name, context ):
            def patch_by_name( new_func) :
                old_func = getattr( cls, func_name )
                def patched_func( self, *args, **kwargs ):
                    return new_func( self, old_func, context, *args, **kwargs )
                setattr( cls, func_name, patched_func )
            return patch_by_name

        setattr( clazz, "patch", on_class_patcher )

class Another:
    def log( self, level, info ):
        print( level, info )

Now, let's patch something:

obj = Original()
obj.f( 1, b = "hello" )

p = Patcher()
p.prepare_class( clazz = Original )

logger = Another()

@Original.patch( "f", context = logger  )
def new_f( self, old_f, context, a, b ):
    print( "new_f", a, b )
    context.log( "zzz", a )
    old_f( self, a, b )

obj = Original()
obj.f( 1, b = "hello" )

And the output is:

f 1 hello
g hello
new_f 1 hello
zzz 1
f 1 hello
g hello
j4zzcat
  • 322
  • 2
  • 14
1

One option that might not always be the best in a language like Python is to use the @override decorator from this non-standard package. But this is a viable option only if your two functions work on different types or different number of arguments. Other than that, there's not much you can do, besides renaming your function.

Paul Manta
  • 30,618
  • 31
  • 128
  • 208