This recipe enables proper protection of attributes that are mangled with Pythons privacy indicator: the single underscore _.
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 | class WithProtection(object):
'''
WithProtection can be used as a base class for all classes that
want true protection of user defined attributes mangled with a
single underscore "_".
'''
def __new__(cls, *args, **kwd):
obj = object.__new__(cls)
cls.__init__(obj, *args, **kwd)
def __getattr__(self, name):
attr = getattr(obj, name)
if name.startswith("_"):
if name.startswith("__") and name.endswith("__"):
return attr
else:
return attr
raise AttributeError, "Attribute %s is not public."%name
def __setattr__(self, name, value):
if name.startswith("__") and name.endswith("__"):
raise AttributeError,"magic attributes are write protected."
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("WithProtection(%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__
proxy = Proxy()
return proxy
#
# Example
#
class Counter(WithProtection):
def __init__(self, start=0):
self._n = start
def inc(self):
self._n+=1
return self._n
def dec(self):
self._n-=1
return self._n
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
def inc(self):
self._n+=self.step
return self._n
def __add__(self, k):
return self._n+k
def dec(self):
self._n-=self.step
return self._n
def steps(self):
return self._n/self.step
################################################################
# Python Session
################################################################
>>> sc = SteppedCounter()
>>> sc.step
1
>>> sc._n
Traceback (most recent call last):
....
AttributeError: Attribute _n is not public.
>>> sc.inc()
1
>>> sc+8
9
|
This recipe is a close successor of my former recipe
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496920
but it doesn't suffer from decoritis. Instead it uses the usual Python convention to indicate private object attributes by mangling their name using a single underscore ( or a double underscore as in __NAME without indicating __MAGIC__ ).
You should include a few caveats. There are some warnings you should probably include with this recipe. First off it will break some of the things you might expect to do if you thought you had just created a SteppedCounter object e.g. none of 'isinstance', 'dir' or 'help' will work as you might have expected.
You would also have to be very careful if the class ever passes 'self' out to another function as the 'self' it passes out will be the original unprotected object.
Also, and I think quite importantly, people need to be aware that this recipe is simply performing a more advance way to hide attributes against accidental intrusion, it doesn't add any kind of security and the attributes are still easily accessible if you want to get at them.
e.g.
Ah, I see. These are very severe criticisms. Thanks, Duncan. I will investigate whether the cell inspection hack can be avoided. But I don't see any way of preventing leaking abstraction in case of returning a Counter instance without additional coding/wrapping. This is very bad.