Search code examples
pythonmetaprogrammingdrygetattrsetattr

setattr and getattr with methods


I have a boiler platey class that delegates some actions to a reference class. It looks like this:

class MyClass():

    def __init__(self, someClass):            
        self.refClass = someClass

    def action1(self):
        self.refClass.action1()

    def action2(self):
        self.refClass.action2()

    def action3(self):
        self.refClass.action3()

This is the refClass:

class RefClass():

    def __init__(self):
        self.myClass = MyClass(self)

    def action1(self):
        #Stuff to execute action1

    def action2(self):
        #Stuff to execute action2

    def action3(self):
        #Stuff to execute action3

I'd like to use Python Metaprogramming to make this more elegant and readable, but I'm not sure how.

I've heard of setattr and getattr, and I think I could do something like

class MyClass():

    def __init__(self, someClass):            
        self.refClass = someClass

    for action in ['action1', 'action2', 'action3']:
        def _delegate(self):
            getattr(self.refClass, action)()

And then I know I need to do this from somewhere, I guess:

MyClass.setattr(action, delegate)

I just can't totally grasp this concept. I understand the basics about not repeating code, and generating the methods with a for loop with functional programming, but then I don't know how to call this methods from elsewhere. Heeeelp!


Solution

  • Python already includes support for generalized delegation to a contained class. Just change the definition of MyClass to:

    class MyClass:
    
        def __init__(self, someClass):            
            self.refClass = someClass  # Note: You call this someClass, but it's actually some object, not some class in your example
    
        def __getattr__(self, name):
            return getattr(self.refClass, name)
    

    When defined, __getattr__ is called on the instance with the name of the accessed attribute any time an attribute is not found on the instance itself. You then delegate to the contained object by calling getattr to look up the attribute on the contained object and return it. This costs a little each time to do the dynamic lookup, so if you want to avoid it, you can lazily cache attributes when they're first requested by __getattr__, so subsequent access is direct:

    def __getattr__(self, name):
         attr = getattr(self.refClass, name)
         setattr(self, name, attr)
         return attr