Search code examples
pythonclassmethodsclass-method

Python Adding Methods at runtime


I'm trying to dynamically add methods to classes at runtime, and am seeing some issues:

#Here we define a set of symbols within an exec statement and put them into the dictionary d
d = {}
exec "def get_%s(self): return self.%s" % (attr_name, attr) in d

#Now, we bind the get method stored in d['get_%s'] to our object (class)
func = d['get_%s' % (attr_name)].__get__(d['get_%s' % (attr_name)], class)
setattr(class_instance, func.__name__, func)

When I try to call the generated get method, I see the below:

Traceback (most recent call last):
  File "Textere_AdvancedExample.py", line 77, in <module>
    count = fact.get_counter()
  File "<string>", line 1, in get_counter
AttributeError: 'function' object has no attribute '_counter'

Edit

Based on some of the exceptional responses given so far, I think I need to clarify why I'm doing things this way.

I'm trying to build an annotation like the below example:

@getters
@singleton
class A() {

    def __init__(self):
        self._a = "a"
        self._b = "b"
 }

Based on the names present in the class, the annotation will build getters for the private class variables at runtime and bind them to the singleton instance.

The strategy I've taken is to have an Application Context class with a set of dicts. Then, the context is passed in to the annotation, which adds the instance & class into these dicts.

On startup, the Application Context is then responsible for reading the dictionaries and then building & binding get methods to the respective singleton object.

Edit2

So this development started after some discussions with friends of mine who are Java developers regarding two libraries in particular: Spring & Lombok

I wanted to see if these particular pieces of functionality could be implemented in Python. So the application context came about originally from trying to get a functionality similar to Spring's autowire annotation. I got this working without issue.

Then, I got the generating the getters and setters and realized that I was going to have a fundamental difference in Python from the Java implementation: Lombok does this at compile time and Python is not compiled. This meant that I had to dynamically generate methods based on what's being annotated and bind them to objects manually, all at runtime. Thus, you see this sort of warping of the Java implementation.

For those interested, The full code can be found here


Solution

  • You can easily dynamically add static methods or class methods:

    class A:
        pass
    
    @staticmethod
    def foo0(x):
        return x * 2
    
    a = A()
    A.foo = foo0
    a.foo(3)
    

    return 6

    class A:
        val = 3
    
    @classmethod
    def foo0(cls, x):
        return x * cls.val
    
    a = A()
    A.foo = foo0
    a.foo(2)
    

    return 6

    You can also add specific methods to an instance of a class (almost the same way)

    class A:
        pass
    
    a = A()
    a.foo = (lambda x: 2*x)
    a.foo(3)
    

    returns 6

    You can also add an instance method to a class, through the use of the types module (in fact, this generic way can be used to create also static and class methods, as well as instance only methods):

    class A:
        def __init__(self, val):
            self.val = val
    
    
    a = A(3)
    A.foo = types.MethodType((lambda self, x: self.val * x), None, A)
    a.foo(2)
    

    returns 6

    But this is really monkey patching, that is a quick and dirty hack that should only be used when you need to pass slightly changed classes and you are not allowed to change the name. The nice and clean way to add functionalities to a class is inheritance


    Just to make this answer better, the above is valid for Python 2.

    For Python 3, only the first way to create class and static method can be used, because the types module has lost many types.

    And you create an instance method as simply as:

    class A:
        def __init__(self, val):
            self.val = val
    
    
    a = A(3)
    A.foo = (lambda self, x: self.val * x)
    a.foo(2)
    

    returns 6. No need for special construct here