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

Metaclass that allows for private attributes. Classes can ensure privacy of their data by checking the stack frame inside __setattr__ and __getattribute__ to see if the function requesting the attribute was defined as part of the class.

Python, 121 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
# privates.py

import sys
import itertools

class PrivateAccessError(Exception):
    pass

class PrivateDataMetaclass(type):
    def __new__(metacls,name,bases,dct):

        function = type(lambda x:x)

        privates = set(dct.get('__private__',()))

        codes = set()
        for val in dct.itervalues():
            if isinstance(val,function):
                codes.add(val.func_code)

        getframe = sys._getframe
        count = itertools.count

        def __getattribute__(self,attr):
            if attr in privates:
                for i in count(1):
                    code = getframe(i).f_code
                    if code in codes:
                        break
                    if code.co_name != '__getattribute__':
                        raise PrivateAccessError(
                            "attribute '%s' is private" % attr)
            return super(cls,self).__getattribute__(attr)

        def __setattr__(self,attr,val):
            if attr in privates:
                for i in count(1):
                    code = getframe(i).f_code
                    if code in codes:
                        break
                    if code.co_name != '__setattr__':
                        raise PrivateAccessError(
                            "attribute '%s' is private" % attr)
            return super(cls,self).__setattr__(attr,val)

        dct['__getattribute__'] = __getattribute__
        dct['__setattr__'] = __setattr__

        cls = type.__new__(metacls,name,bases,dct)

        return cls


# And now for a few tests

import traceback

class A(object):
    __metaclass__ = PrivateDataMetaclass
    __private__ = ['internal']

    def __init__(self,n):
        self.internal = n

    def inc(self):
        self.internal += 1

    def res(self):
        return self.internal

class B(A):
    __private__ = ['internal2']

    def __init__(self,n,m):
        super(B,self).__init__(n)
        self.internal2 = m

    def inc(self):
        super(B,self).inc()
        self.internal2 += 2

    def res(self):
        return self.internal2 + super(B,self).res()

    def bad(self):
        return self.internal2 + self.internal

a = A(1)
a.inc()

print "Should print 2:"
print a.res()
print

print "Should raise PrivateAccessError:"
try:
    print a.internal
except PrivateAccessError:
    traceback.print_exc()
print

b = B(1,1)
b.inc()

print "Should print 5:"
print b.res()
print

print "Should raise PrivateAccessError:"
try:
    print b.internal2
except PrivateAccessError:
    traceback.print_exc()
print

print "Should raise PrivateAccessError:"
try:
    print b.bad()
except PrivateAccessError:
    traceback.print_exc()
print

Many people have clamored for private attributes in Python. Some of them might have actually had a good reason to do so. Although Python has no support for private attributes, and its design makes it problematic to add it programmatically, it can be done.

What PrivateDataMetaclass does is to take note of all the functions that were defined as part of the class defintion. It then creates custom __getattribute__ and __setattr__ for the new class that crawl up the stack frame and looking for the function (really the code object of the function) that accessed the attribute. If that function was not one of the functions defined in the class, it raises PrivateAccessError.

One declares which attributes are private by assigning a list of symbols to __private__ within the class definition. (Similar to how slots are defined.)

Limitations: * Classes are prevented from defining their own __setattr__ and __getattribute__. * Classes and subclasses should not use the same names for their private variables. * Can't access private from nested functions. This could be added fairly easily. * Private attribute access is pretty slow, but that's obvious. * Pretty easy to thwart.

1 comment

Éric Araujo 10 years, 9 months ago  # | flag

Neat trick, but it’s a bad idea to define your own __magic__ names. Those can break without warning.