Home > Blockchain >  Using decorators that are aware of `self`'s state
Using decorators that are aware of `self`'s state

Time:01-20

In brief, I have a DataFormatter class that has two possible states: train or infer, that should act similarly to many of the sklearn libraries that have fit and transform functions: if mode is train I want to store in self.metadata a list of the function calls and args that were made, so that they can simply be reapplied verbatim and in order at infer time.

So minimally, I have:

import inspect


class DataFormatter:
    def __init__(self, mode, data=None):
        self.data = data
        self.metadata = []

    # The decorator function: broken, but something like--
    def meta(self, f, *args):
        def wrapper(*args):
            return f(*args)

        if self.mode == 'train':
            print('caching metadata')
            meta = {
                f.__name__: {
                    param: arg for param, arg in zip(
                        inspect.getfillargspec(f).args, args)}}
            self.metadata.append(meta)
        return wrapper

    @meta
    def drop(self, cols):
        self.data = self.data.drop(cols)

Then if I use:

formatter = DataFormatter('train', my_data)
formatter.drop(['col1', 'col5'])
print(formatter.metadata)

...I would like to get: [{'drop': {'cols': ['col1', 'col5']}}]

I have tried various permutations and placements of self, and pulling the decorator func outside the class altogether, but no luck so far.

@kindall says "You can't get self at decoration time because the decorator is applied at function definition time. No self exists yet; in fact, the class doesn't exist yet." (Possible to create a @synchronized decorator that's aware of a method's object?), so not even sure if this is possible...

CodePudding user response:

What will "see" a self is the decorated function - and it is represented by the function you call "wrapper":

import inspect


# The decorator function: should be out of the class body

def meta( f): ,
    def wrapper(self, *args):
        # call the decorated function:
        # (but you could run other code, including inspecting
        # and modifying arguments, here as well)
        result = f(self, *args)
        # now, your original method had run, and you have
        # access to self:
        if self.mode == 'train':
            print('caching metadata')
            meta = {
                f.__name__: {
                    param: arg for param, arg in zip(
                        inspect.getfillargspec(f).args, args)}}
            self.metadata.append(meta)
        return result
    return wrapper

class DataFormatter:
    def __init__(self, mode, data=None):
        self.data = data
        self.metadata = []

    @meta
    def drop(self, cols):
        self.data = self.data.drop(cols)

That will work.

  • Related