File: LP6E/Chapter39/access1.py
"""
Class decorator with Private attribute declarations.
Privacy for attributes fetched from class instances.
See self-test code at end of file for a usage example.
Rebinding is: Doubler = Private('data', 'size')(Doubler).
Private returns onDecorator, onDecorator returns onInstance,
and each onInstance instance embeds a new Doubler instance.
"""
traceMe = False
def trace(*args):
if traceMe: print(f'[{' '.join(map(str, args))}]') # Python 3.12+ f-string
def Private(*privates): # privates in enclosing scope
def onDecorator(aClass): # aClass in enclosing scope
class onInstance: # wrapped in instance attribute
def __init__(self, *args, **kargs):
self.wrapped = aClass(*args, **kargs)
def __getattr__(self, attr): # My attrs don't call getattr
trace('get:', attr) # Others assumed in wrapped
if attr in privates:
raise TypeError('private attribute fetch, ' + attr)
else:
return getattr(self.wrapped, attr)
def __setattr__(self, attr, value): # Outside accesses
trace('set:', attr, value) # Others run normally
if attr == 'wrapped': # Allow my attrs
self.__dict__[attr] = value # Avoid looping
elif attr in privates:
raise TypeError('private attribute change, ' + attr)
else:
setattr(self.wrapped, attr, value) # Wrapped obj attrs
return onInstance
return onDecorator
if __name__ == '__main__':
traceMe = True
@Private('data', 'size') # Doubler = Private(...)(Doubler)
class Doubler:
def __init__(self, label, start):
self.label = label # Accesses inside the subject class
self.data = start # Not intercepted: run normally
def size(self):
return len(self.data) # Method bodies run with no checking
def double(self): # Because privacy not inherited
for i in range(self.size()):
self.data[i] = self.data[i] * 2
def display(self):
print(f'{self.label} => {self.data}')
print('Making instances...')
X = Doubler('X is', [1, 2, 3])
Y = Doubler('Y is', [-10, -20, -30])
# The following all succeed properly
print('\nExploring X instance...')
print(X.label) # Accesses outside subject class
X.display(); X.double(); X.display() # Intercepted: validated, delegated
print('\nExploring Y instance...')
print(Y.label)
Y.display(); Y.double()
Y.label = 'Hack'
Y.display()
# The following all fail properly
"""
print(X.size()) # Prints "TypeError: private attribute fetch, size"
print(X.data)
X.data = [1, 1, 1] # Prints "TypeError: private attribute change, data"
X.size = lambda S: 0
print(Y.data)
print(Y.size())
"""