Search code examples
pythonclassmethodsdescriptor

Return a custom value when a class method is accessed as an attribute, but still allow for it to perform a computation when called?


Specifically, I would want MyClass.my_method to be used for lookup of a value in the class dictionary, but MyClass.my_method() to be a method that accepts arguments and performs a computation to update an attribute in MyClass and then returns MyClass with all its attributes (including the updated one).

I am thinking that this might be doable with Python's descriptors (maybe overriding __get__ or __call__), but I can't figure out how this would look. I understand that the behavior might be confusing, but I am interested if it is possible (and if there are any other major caveats).

I have seen that you can do something similar for classes and functions by overriding __repr__, but I can't find a similar way for a method within a class. My returned value will also not always be a string, which seems to prohibit the __repr__-based approaches mentioned in these two questions:


Solution

  • Thank you Joel for the minimal implementation. I found that the remaining problem is the lack of initialization of the parent, since I did not find a generic way of initializing it, I need to check for attributes in the case of list/dict, and add the initialization values to the parent accordingly.

    This addition to the code should make it work for lists/dicts:

    def classFactory(parent, init_val, target):
        class modifierClass(parent):
            def __init__(self, init_val):
                super().__init__()
                dict_attr = getattr(parent, "update", None)
                list_attr = getattr(parent, "extend", None)
                if callable(dict_attr):  # parent is dict
                    self.update(init_val)
                elif callable(list_attr):  # parent is list
                    self.extend(init_val)
                self.target = target
    
            def __call__(self, *args):
                self.target.__init__(*args)
    
        return modifierClass(init_val)
    
    
    class myClass:
        def __init__(self, init_val=''):
            self.method = classFactory(init_val.__class__, init_val, self)
    

    Unfortunately, we need to add case by case, but this works as intended.

    A slightly less verbose way to write the above is the following:

    def classFactory(parent, init_val, target):
        class modifierClass(parent):
            def __init__(self, init_val):
                if isinstance(init_val, list):
                    self.extend(init_val)
                elif isinstance(init_val, dict):
                    self.update(init_val)
                self.target = target
    
            def __call__(self, *args):
                self.target.__init__(*args)
    
        return modifierClass(init_val)
    
    
    class myClass:
        def __init__(self, init_val=''):
            self.method = classFactory(init_val.__class__, init_val, self)