Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use an instance method as a decorator within another class

I am trying to create a class (MySerial) that instantiates a serial object so that I can write/read to a serial device (UART). There is an instance method that is a decorator which wraps around a function that belongs to a completely different class (App). So decorator is responsible for writing and reading to the serial buffer.

If I create an instance of MySerial inside the App class, I can't use the decorator instance method that is created from MySerial. I have tried foregoing instance methods and using class methods as explained in this second answer, but I really need to instantiate MySerial, thus create an instance using __init__.

How can this be accomplished? Is it impossible?

  • Create a decorator that is an instance method.
  • Use this decorator within another class

class MySerial():
    def __init__(self):
        pass # I have to have an __init__
    def write(self):
        pass # write to buffer
    def read(self):
        pass # read to buffer
    def decorator(self, func):
        def func_wrap(*args, **kwargs):
            self.write(func(*args, **kwars))
            return self.read()
        return func_wrap

class App():
    def __init__(self):
        self.ser = MySerial()

    @self.ser.decorator  # <-- does not work here.
    def myfunc(self):
        # 'yummy_bytes' is written to the serial buffer via 
        # MySerial's decorator method
        return 'yummy_bytes'

if __name__ == '__main__':
    app = App()
like image 308
Jordan Lee Avatar asked May 31 '18 23:05

Jordan Lee


2 Answers

You can use a staticmethod to wrap decorator. The inner func_wrap function of decorator contains an additional parameter in its signature: cls. cls can be used to access the ser attribute of the instance of App, and then the desired methods write and read can be called from cls.ser. Also, note that in your declarations, MySerial.write takes no paramters, but is passed the result of the wrapped function. The code below uses *args to prevent the TypeError which would otherwise be raised:

class MySerial():
   def __init__(self):
     pass # I have to have an __init__
   def write(self, *args):
     pass # write to buffer
   def read(self):
     pass # read to buffer
   @staticmethod
   def decorator(func):
     def func_wrap(cls, *args, **kwargs):
        cls.ser.write(func(cls, *args, **kwargs))
        return cls.ser.read()
     return func_wrap

class App():
  def __init__(self):
     self.ser = MySerial()
  @MySerial.decorator 
  def myfunc(self):
    # 'yummy_bytes' is written to the serial buffer via 
    # MySerial's decorator method
    return 'yummy_bytes'

App().myfunc()
like image 79
Ajax1234 Avatar answered Oct 26 '22 02:10

Ajax1234


The reason this does not work is because you are refering to self in the class body, where it is not defined. Here are two solutions.

Store the serial object as class attribute

If you store the MySerial instance as a class attribute, then it sill be accessible in the class body:

class App():
    ser = MySerial()

    @ser.decorator
    def myfunc(self):
        return 'yummy_bytes'

Decorate on each instantiation

Or if you need a different MySerial instance for every App instance, then you will need to wait for the instance to be created to define an instance attribute my_func. This means the function is decorated dynamically on every instance creation, in which case, the @ decorator syntax must be replaced by a function call.

class App():
    def __init__(self):
        self.ser = MySerial()
        self.my_func = self.ser.decorator(self.myfunc)

    def myfunc(self):
        return 'yummy_bytes'

This solution generalizes to decorating multiple methods or conditionally deactivating serializing, say in a test environment.

import env

class App():
    def __init__(self):
        self.ser = MySerial()

        to_decorate = [] if env.test else ['myfunc']

        for fn_name in to_decorate:
            fn = getattr(self, fn_name)
            setattr(self, fn_name, self.ser.decorator(fn))
like image 24
Olivier Melançon Avatar answered Oct 26 '22 00:10

Olivier Melançon