[Python-Dev] Monkeypatching idioms -- elegant or ugly?

Robert Brewer fumanchu at aminus.org
Tue Jan 15 19:13:25 CET 2008


Guido van Rossum:
> I ran into the need of monkeypatching a large number of classes (for
> what I think are very good reasons :-) and invented two new recipes.
> These don't depend on Py3k, and the second one actually works all the
> way back to Python 2.2. Neither of these allows monkeypatching
> built-in types like list. If you don't know what monkeypatching is,
> see see http://en.wikipedia.org/wiki/Monkey_patch.
> 
> I think it's useful to share these recipes, if only to to establish
> whether they have been discovered before, or to decide whether they
> are worthy of a place in the standard library. I didn't find any
> relevant hits on the ASPN Python cookbook.
> 
> First, a decorator to add a single method to an existing class:
> 
> def monkeypatch_method(cls):
>     def decorator(func):
>         setattr(cls, func.__name__, func)
>         return func
>     return decorator
> 
> To use:
> 
> from <somewhere> import <someclass>
> 
> @monkeypatch_method(<someclass>)
> def <newmethod>(self, args):
>     return <whatever>
> 
> This adds <newmethod> to <someclass>

I like it, but my first thought was, "and if that method already
exists?" I'd extend monkeypatch_method to store a reference to the old
method(s):

    def monkeypatch_method(cls):
        """Add the decorated method to the given class; replace as
needed.
        
        If the named method already exists on the given class, it will
        be replaced, and a reference to the old method appended to a
list
        at cls._old_<name>. If the "_old_<name>" attribute already
exists
        and is not a list, KeyError is raised.
        """
        def decorator(func):
            fname = func.__name__
            
            old_func = getattr(cls, fname, None)
            if old_func is not None:
                # Add the old func to a list of old funcs.
                old_ref = "_old_%s" % fname
                old_funcs = getattr(cls, old_ref, None)
                if old_funcs is None:
                    setattr(cls, old_ref, [])
                elif not isinstance(old_funcs, list):
                    raise KeyError("%s.%s already exists." %
                                   (cls.__name__, old_ref))
                getattr(cls, old_ref).append(old_func)
            
            setattr(cls, fname, func)
            return func
        return decorator

I chose a renaming scheme somewhat at random. The list allows you (or
someone else ;) to call monkeypatch repeatedly on the same cls.method
(but it's not thread-safe).

And although it might seem to be making monkeypatches easier to perform,
at least it's very explicit about what's going on as long as you keep
"monkeypatch" in the name.


Robert Brewer
fumanchu at aminus.org


More information about the Python-Dev mailing list