2

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:

  • use a decorator to register the new instance of StatsVar into ctx (Rather than Ctx)

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?

5
  • You could make a separate function that instantiates the Ctx class and then passes it back. I know that's not quite how you want it. Commented Apr 15, 2022 at 3:46
  • @Rashid'Lee'Ibrahim Thanks for your suggestion, and I update a solution. However, I still wonder if there is a more elegant solution here Commented Apr 15, 2022 at 3:59
  • Are you ever going to have more than one instance of Ctx? If not, perhaps you should create the instance before defining StatsVar so you can add the instances of the latter to the single instance of the former. Or you could do without the Ctx class all together, and just use global functions and variables. If you do want to be able to have multple Ctx instances, then it's going to be very hard for any decorator to do anything useful since there's no clear way for it to know which Ctx instance to register a new StatsVar instance to. Commented Apr 15, 2022 at 7:29
  • in the @-way you forgot the @classmethod Commented Apr 15, 2022 at 9:33
  • ... and Ctx should be defined before the StatsVar to perform sugar decoration Commented Apr 15, 2022 at 9:36

2 Answers 2

1

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:
    ....
Sign up to request clarification or add additional context in comments.

3 Comments

setter and getter protocol can be omîtted but than the implementation is a bit less natural
Thanks, I think there are only two options to achieve my target. One is to 1) build Ctx as a singleton class, so that I can register variables within it or 2) register variables in the class level by building a class method. Both of them work, tks.
@david decorators are quite flexible and more than one ways exist, choose the one which you think has "better" syntax
0

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]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.