Search code examples
pythonmagic-methodssetattr

How to differentiate between a property being set with ___setattr__ from inside the class and outside the class?


Is there any way in __setattr__() to differentiate between an attribute set from inside the class or a child/inheriting class, and an attribute set from outside the current or a child class?

I want to change how setting attributes works from the "outside", in my case of making a module, I want the user to have different logic when setting a attribute than when it's set from inside the class.

For example:
i.x = 5 should assign 5 normally when called from within the class and i is a instance of it, but when called from another class it should, say, subtract 5 instead of set to 5.


Solution

  • A bit lowlevel, but you could use inspect module:

    import inspect
    
    class A:
    
        def __init__(self):
            self.__x = 0
    
        @property
        def x(self):
            return self.__x
    
        @x.setter
        def x(self, value):
            f = inspect.currentframe()
            if 'self' in f.f_back.f_locals and issubclass(type(f.f_back.f_locals['self']), A):
                print('Called from class!')
                self.__x = -value
            else:
                print('Called from outside!')
                self.__x = value
    
        def fn(self):
            print('Calling A.x from inside:')
            self.x = 10
    
    class B(A):
        def __init__(self):
            super().__init__()
    
        def fn2(self):
            print('Calling B.x from inside:')
            self.x = 15
    
    a = A()
    print("A.x after init:", a.x)
    print('Calling A.x from outside')
    a.x = 10
    print("A.x called from the outside:", a.x)
    a.fn()
    print("A.x called from the inside:", a.x)
    
    b = B()
    print("B.x after init:", b.x)
    print('Calling B.x from outside')
    b.x = 20
    print("B.x called from the outside:", b.x)
    b.fn2()
    print("B.x called from the inside:", b.x)
    

    Prints:

    A.x after init: 0
    Calling A.x from outside
    Called from outside!
    A.x called from the outside: 10
    Calling A.x from inside:
    Called from class!
    A.x called from the inside: -10
    B.x after init: 0
    Calling B.x from outside
    Called from outside!
    B.x called from the outside: 20
    Calling B.x from inside:
    Called from class!
    B.x called from the inside: -15