Home > Blockchain >  How to block methods while attribute is True in python class
How to block methods while attribute is True in python class

Time:09-17

I have a class with some attributes and methods to change those attributes. (This is a minimal exemple)

class MyClass:
    def __init__(self) -> None:
        self.acting = False
        self.att1 = None
        self.att2 = None
        self.att3 = None

    def set_att1(self, value):
        self.att1 = value

    def set_att2(self, value):
        self.att2 = value

    def set_att3(self, value):
        self.att3 = value

    def switch_acting(self):
        self.acting = not self.acting
    

I want to block the use of methods that change certain attributes while self.acting is True. This block could be an error raised or a False return.

I know that i can just put an if statement in the beginning of each method to check for self.acting or use a decorator, but I want to do it in an automatic way, in which it would block the use of all functions that start with "set" without me having to add code to all of them.

How can I do this?

CodePudding user response:

You can use __getattribute__ here, something to the effect of:

In [1]: class MyClass:
   ...:     def __init__(self) -> None:
   ...:         self.acting = False
   ...:         self.att1 = None
   ...:         self.att2 = None
   ...:         self.att3 = None
   ...:
   ...:     def __getattribute__(self, attribute):
   ...:         if attribute.startswith("set_") and not self.acting:
   ...:             raise RuntimeError(f"{self} is not acting")
   ...:         return super().__getattribute__(attribute)
   ...:
   ...:     def set_att1(self, value):
   ...:         self.att1 = value
   ...:
   ...:     def set_att2(self, value):
   ...:         self.att2 = value
   ...:
   ...:     def set_att3(self, value):
   ...:         self.att3 = value
   ...:
   ...:     def switch_acting(self):
   ...:         self.acting = not self.acting
   ...:

In [2]: instance = MyClass()

In [3]: instance.set_att1(10)
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-3-ead9d2b57142> in <module>
----> 1 instance.set_att1(10)

<ipython-input-1-585c5de84d26> in __getattribute__(self, attribute)
      8     def __getattribute__(self, attribute):
      9         if attribute.startswith("set_") and not self.acting:
---> 10             raise RuntimeError(f"{self} is not acting")
     11         return super().__getattribute__(attribute)
     12

RuntimeError: <__main__.MyClass object at 0x7f9a00520af0> is not acting

In [4]: instance.switch_acting()

In [5]: instance.set_att1(10)

In [6]: instance.att1
Out[6]: 10

However, as noted in the comments, this will check the self.acting flag when the attribute is looked up, which is not necessarily when the method is called.

We can more carefully handle the fact that the verification should happen when the method is called, not accessed:

class MyClass:
    def __init__(self) -> None:
        self.acting = False
        self.att1 = None
        self.att2 = None
        self.att3 = None

    def __getattribute__(self, attribute):
        if attribute.startswith("set_"):
            bound_method  = super().__getattribute__(attribute).__get__(self, type(self))
            def _wrapper(*args, **kwargs):
                if not self.acting:
                    raise RuntimeError(f"{self} is not acting")
                return bound_method(*args, **kwargs)
            return _wrapper
        return super().__getattribute__(attribute)

    def set_att1(self, value):
        self.att1 = value

    def set_att2(self, value):
        self.att2 = value

    def set_att3(self, value):
        self.att3 = value

    def switch_acting(self):
        self.acting = not self.acting

A demonstration:

In [29]: instance = MyClass()

In [30]: instance.set_att1(10)
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-30-ead9d2b57142> in <module>
----> 1 instance.set_att1(10)

<ipython-input-28-53d1a32f1498> in _wrapper(*args, **kwargs)
     11             def _wrapper(*args, **kwargs):
     12                 if not self.acting:
---> 13                     raise RuntimeError(f"{self} is not acting")
     14                 return bound_method(*args, **kwargs)
     15             return _wrapper

RuntimeError: <__main__.MyClass object at 0x7f99e05bf340> is not acting

In [31]: instance.switch_acting()

In [32]: instance.set_att1(10)

In [33]: instance.att1
Out[33]: 10

In [34]: f = instance.set_att2

In [35]: instance.switch_acting()

In [36]: f(20)
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-36-a5c3c0536773> in <module>
----> 1 f(20)

<ipython-input-28-53d1a32f1498> in _wrapper(*args, **kwargs)
     11             def _wrapper(*args, **kwargs):
     12                 if not self.acting:
---> 13                     raise RuntimeError(f"{self} is not acting")
     14                 return bound_method(*args, **kwargs)
     15             return _wrapper

RuntimeError: <__main__.MyClass object at 0x7f99e05bf340> is not acting

In [37]: instance.switch_acting()

In [38]: f(20)

In [39]: instance.att2
Out[39]: 20

EDIT: I think I did the opposite of what you wanted, but the same should work, just check if self.acting instead of if not self.acting

CodePudding user response:

This is a case for class decorator.

It would take the class as input and return a new class by iterating over all members and replacing those that are methods-whose-name-is-set_* with new methods that call the old one only if self.acting is true.

  • Related