Search code examples
pythonmonkeypatching

Add attribute to python class


Consider the following code:

class Foo():

    pass


Foo.entries = dict()


a = Foo()
a.entries['1'] = 1

b = Foo()
b.entries['3'] = 3

print(a.entries)

This will print:

{'1': 1, '3': 3}

because the entries is added as static attribute. Is there a way monkey patch the class definition in order to add new attributes (without using inheritance).

I managed to find the following way but it looks convoluted to me:

def patch_me(target, field, value):
    def func(self):
        if not hasattr(self, '__' + field):
            setattr(self, '__' + field, value())
        return getattr(self, '__' + field)
    setattr(target, field, property(func))

patch_me(Foo, 'entries', dict)

Solution

  • Ordinarily, attributes are added either by the __init__() function or after instantiating:

    foo = Foo()
    foo.bar = 'something'  # note case
    

    If you want to do this automatically, inheritance is by far the simplest way to do so:

    class Baz(Foo):
        def __init__(self):
            super().__init__()  # super() needs arguments in 2.x
            self.bar = 'something'
    

    Note that classes don't need to appear at the top level of a Python module. You can declare a class inside a function:

    def make_baz(value):
        class Baz(Foo):
            def __init__(self):
                super().__init__()  # super() needs arguments in 2.x
                self.bar = value()
        return Baz()
    

    This example will create a new class every time make_baz() is called. That may or may not be what you want. It would probably be simpler to just do this:

    def make_foo(value):
        result = Foo()
        result.bar = value()
        return result
    

    If you're really set on monkey-patching the original class, the example code you provided is more or less the simplest way of doing it. You might consider using decorator syntax for property(), but that's a minor change. I should also note that it will not invoke double-underscore name mangling, which is probably a good thing because it means you cannot conflict with any names used by the class itself.