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.

 Download
Download Copy to clipboard
Copy to clipboard
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.