How do I extend a class that I never instantiate myself?

Chris Angelico rosuav at gmail.com
Sun Oct 11 02:32:09 EDT 2015


On Sun, Oct 11, 2015 at 4:53 PM, dieter <dieter at handshake.de> wrote:
> Otherwise, you can likely use a technique called "monkey patching".
> This is dynamically changing code at startup time.
> In your case, it could look like:
>
>    from ... import Node
>
>    def new_method(self, ...):
>       ...
>
>    Node.new_method = new_method
>
> From that moment on, the "Node" class has a new method "new_method".

And if you're going to do this for a good number of methods, I'd
recommend something like this:

def monkeypatch(cls):
    def deco(func):
        setattr(cls, func.__name__, func)
        return func
    return deco

@monkeypatch(Node)
def new_method(self, ...):
    ...

@monkeypatch(Node)
def another_new_method(self, ...):
    ...

@monkeypatch(Node)
def yet_another(self, ...):
    ...

@monkeypatch(Node)
def and_another(self, ...):
    ...

If you REALLY want to look like Ruby, you could actually do something
rather sneaky. When a decorator is called, the name has yet to be
bound; conceptually, it's like defining the function/class and then
using "x = deco(x)" at the end, but you can actually access the
previous use of that name. So check this out:

# -- cut --
class Foo:
    def old_method(self):
        print("Old method.")

print("Before we begin patching, Foo is %x." % id(Foo))

def monkeypatch(cls):
    # NOTE: Needs to be able to find its own globals, so you
    # can't use this inside a function or import it from another
    # module. Play with sys._getframe if you want to.
    old = globals()[cls.__name__]
    print("Inside monkeypatch, old is %x, and cls is %x." % (id(old), id(cls)))
    for attr in dir(cls):
        # Don't override dunders. TODO: Figure out if they were
        # set here, and if so, *do* override.
        if attr.startswith("__") and attr.endswith("__"): continue
        setattr(old, attr, getattr(cls, attr))
    return old

@monkeypatch
class Foo:
    def new_method(self):
        print("New method!")

print("After patching, Foo is %x." % id(Foo))

f = Foo()
f.old_method()
f.new_method()
# -- cut --

However, don't blame me if other programmers chase you down with
torches and pitchforks.

ChrisA



More information about the Python-list mailing list