This recipe presents a way to introduce proper attribute access protection levels using a generic proxy object. By default all attributes are "protected" in the C++ sense and can be declared as public using either a function decorator or a class attribute.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | def public(f):
'''
Decorator used to assign the attribute __public__ to methods.
'''
f.__public__ = True
return f
class Protected(object):
'''
Base class of all classes that want to hide protected attributes from
public access.
'''
def __new__(cls, *args, **kwd):
obj = object.__new__(cls)
cls.__init__(obj, *args, **kwd)
def __getattr__(self, name):
attr = getattr(obj, name)
if hasattr(attr, "__public__"):
return attr
elif hasattr(cls, "__public__"):
if name in cls.__public__:
return attr
raise AttributeError, "Attribute %s is not public."%name
def __setattr__(self, name, value):
attr = getattr(obj, name)
cls.__setattr__(self, name, value)
# Magic methods defined by cls must be copied to Proxy.
# Delegation using __getattr__ is not possible.
def is_own_magic(cls, name, without=[]):
return name not in without and\
name.startswith("__") and name.endswith("__")
Proxy = type("Protected(%s)"%cls.__name__,(),{})
for name in dir(cls):
if is_own_magic(cls,name, without=dir(Proxy)):
try:
setattr(Proxy, name, getattr(obj,name))
except TypeError:
pass
Proxy.__getattr__ = __getattr__
Proxy.__setattr__ = __setattr__
return Proxy()
#
# Example
#
class Counter(Protected):
__public__ = ["y"]
def __init__(self, start=0):
self.n = start
self.y = 0
@public
def inc(self):
self.n+=1
return self.n
@public
def dec(self):
self.n-=1
return self.n
@public
def value(self):
return self.n
#
# Enhanced example using inheritance
#
class SteppedCounter(Counter):
def __init__(self, start=0, step=1):
super(SteppedCounter,self).__init__(start=start)
self.step = step
@public
def inc(self):
self.n+=self.step
return self.n
@public
def dec(self):
self.n-=self.step
return self.n
def steps(self):
return self.n/self.step
################################################################
# Python Session
#################################################################
>>> c = Counter()
>>> c.inc()
1
>>> c.dec()
0
>>> c.n
Traceback (most recent call last):
File "<interactive input>", line 1, in ?
File "C:\Python24\Lib\site-packages\pythonwin\private.py", line 17, in __getattr__
raise AttributeError, "Attribute %s is not public."%name
AttributeError: Attribute n is not public.
>>> c.y
0
>>> sc = SteppedCounter(step=2)
>>> sc.inc()
2
>>> sc.value()
2
>>> sc.steps()
Traceback (most recent call last):
File "<interactive input>", line 1, in ?
File "C:\Python24\Lib\site-packages\pythonwin\private.py", line 17, in __getattr__
raise AttributeError, "Attribute %s is not public."%name
AttributeError: Attribute steps is not public.
|
Generations of Pythonistas have mangled their variables with a double underscore "__" to enable data hiding as in C++ or Java. But this was always only an adaequate solution for preventing name clashes between a class and its superclass. The term "privacy" had a very different meaning. Opposite to the standard semantics where each Python variable is essentially public, applying the above recipe it becomes basically "protected" i.e. visible only to the class where it is defined and to subclasses. In order to make an attribute public it has to be made public explicitely using either the public() decorator ( for methods only ) or the class attribute __public__.
Remark (v1.2): handling of magic methods is added.
You know, this is still Python. "protected", even here, just means that normal programming mistakes will be caught and access to the underlying attribute has been obfuscated.
For a particularly round-about example:
Definitely could still be useful, though.