Welcome, guest | Sign In | My Account | Store | Cart

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.

Python, 128 lines
  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.

1 comment

Jeremy Fishman 12 years, 8 months ago  # | flag

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:

>>> c = Counter()
>>> c.n
Traceback (most recent call last):
  File "<interactive input>", line 1, in ?
  File "protected.py", line 17, in __getattr__
    raise AttributeError, "Attribute %s is not public."%name
AttributeError: Attribute n is not public.
>>> c.__class__.__getattr__.im_func.func_closure[0].cell_contents.n
0

Definitely could still be useful, though.

Created by kay schluehr on Thu, 27 Jul 2006 (PSF)
Python recipes (4591)
kay schluehr's recipes (9)

Required Modules

  • (none specified)

Other Information and Tasks