'''
Created on 

@author: haoweizh
'''
class DescSquare:
    def __init__(self, start):                  # Each desc has own state
        self.value = start
    def __get__(self, instance, owner):         # On attr fetch
        return self.value ** 2
    def __set__(self, instance, value):         # On attr assign
        self.value = value                      # No delete or docs

class Client1:
    X = DescSquare(3)          # Assign descriptor instance to class attr

class Client2:
    X = DescSquare(32)         # Another instance in another client class
                               # Could also code 2 instances in same class
c1 = Client1()
c2 = Client2()

print(c1.X)                    # 3 ** 2
c1.X = 4
print(c1.X)                    # 4 ** 2
print(c2.X)                    # 32 ** 2




class DescState:                           # Use descriptor state
    def __init__(self, value):
        self.value = value
    def __get__(self, instance, owner):    # On attr fetch
        print('DescState get')
        return self.value * 10
    def __set__(self, instance, value):    # On attr assign
        print('DescState set')
        self.value = value

# Client class




class InstState:                           # Using instance state
    def __get__(self, instance, owner):
        print('InstState get')             # Assume set by client class
        return instance._Y * 100
    def __set__(self, instance, value):    
        print('InstState set')
        instance._Y = value

# Client class

class CalcAttrs:
    X = DescState(2)                       # Descriptor class attr
    Y = InstState()                        # Descriptor class attr
    def __init__(self):
        self._Y = 3                        # Instance attr
        self.Z  = 4                        # Instance attr

obj = CalcAttrs()
print(obj.X, obj.Y, obj.Z)                 # X and Y are computed, Z is not 
obj.X = 5                                  # X and Y arssignments intercepted
obj.Y = 6
obj.Z = 7
print(obj.X, obj.Y, obj.Z)