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.
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.
Neat trick, but it’s a bad idea to define your own __magic__ names. Those can break without warning.