david
david

Reputation: 954

How to use instance function as decorator in Python?

I have a StatsVar class to maintain the statistic variable, and a Ctx class to register the statistic variables as its attributes as follows.

class StatsVar:
    def __init__(self, v):
        self.v = v

class Ctx:
    def __init__(self):
        self.vars = list()

    def register(self, v):
        self.vars.append(v)

Create a StatsVars and register into an instance of Ctx by the following code:

ctx = Ctx()
var = StatsVar(0.)
ctx.register(var)

What I want to do:

However, I don't know how to obtain the instance of Ctx (like self) to run self.vars.append(v) in the following code:

@Ctx.register
class StatsVar:
    def __init__(self, v):
        self.v = v

class Ctx:
    def __init__(self):
        self.vars = list()

    def register(cls):
        def wrapper(v):
            # !!! HERE, I know it's wrong, but how to obtain `self`?
            self.vars.append(v)
            return cls(v)
        return wrapper

Update

I found a feasible solution is to place StatsVar as an inner class and the creation of StatsVar is left to the instance of Ctx as follows:

class Ctx:
    def __init__(self):
        self.vars = list()

    def statsvar(self):
        manager = self
        class StatsVar:
            def __init__(self, v):
                self.v = v
                manager.vars.append(v)
        return StatsVar

if __name__ == '__main__':
    ctx = Ctx()
    ctx.statsvar()(0.)

    print('')

However, calling ctx.statsvar()(0.) seems terrible and is there any better solution here?

Upvotes: 2

Views: 126

Answers (2)

cards
cards

Reputation: 4988

Thinking in terms of sender and receiver, respectively StatsVar and Ctx, each instance of the sender "fires" a signal to the listener class. To do that, a function decorator can be used to intercept the sender-set_v and update the receiver-set_vars.

def send_register_signal(receiver):
    def wrapper(sender):
        setattr(sender, 'set_v', lambda self, v: (receiver.set_vars(v), setattr(self, 'v', v)))
        return sender
    return wrapper


class Ctx:
    vars = []
    @classmethod
    def set_vars(cls, v):   cls.vars.append(v)
    @classmethod
    def get_vars(cls):      return cls.vars


@send_register_signal(Ctx)
class StatsVar:
    def __init__(self, v):  self.set_v(v)
    def set_v(self, v):     self.v = v
    def get_v(self):        return self.v


# test
a = StatsVar(3)
b = StatsVar('smt')
c = StatsVar(7)
print(Ctx.vars)
print(b.v)
print(c.v)

Output

[3, 'smt', 7]
smt
7

EDIT

To be more consistent with the original question the decorator can be implemented directly in the receiver class.

class Ctx:
    vars = []
    @classmethod
    def set_vars(cls, v):   cls.vars.append(v)
    @classmethod
    def get_vars(cls):      return cls.vars

    @classmethod
    def register(cls, sender):
        setattr(sender, 'set_v', lambda self, v: (cls.set_vars(v), setattr(self, 'v', v)))
        return sender


@Ctx.register
class StatsVar:
    ....

Upvotes: 1

Epsi95
Epsi95

Reputation: 9047

This can be a bad idea but you can use a singleton class like this below--

class Ctx:
    _instance = None
    vars = list()
    
    def __new__(cls, *args, **kwargs):
        if not isinstance(cls._instance, cls):
            cls._instance = object.__new__(cls, *args, **kwargs)
        return cls._instance

    def register(self, v):
        self.vars.append(v)
        

        
class StatsVar():
    def __init__(self,v):
        self.v = v
        Ctx().register(self)
        
    def __repr__(self):
        return f'{self.v}'
        

ctx = Ctx()
StatsVar(0)
StatsVar(1)
StatsVar(2)

print(ctx.vars) # [0, 1, 2]

Upvotes: 0

Related Questions