Você, que acha que toda linguagem OO tem de ter variáveis privadas porque aprendeu com uma que tem, acho que isso é um mandamento sagrado e acha que Python é uma linguagem pecadora porque não tem, suas orações foram atendidas. Usando o código abaixo você estará privado de todo o mal dos atributos públicos, exceto para alguém que esteja REALMENTE disposto a alterá-los, e esse merece ir para o inferno, não? Não? Não acha? Bom... então é melhor deixar isso pra lá porque talvez você acabe levando isso a sério.
Basta salvar o código abaixo em um módulo, importar a função 'private' para seu módulo e definir a variável 'metaclass = private('var1', 'var2', 'var3', ...) no início, listando todos os atributos que deseja deixar privados.
OBS: aos desavisados, esse código, nos moldes do NoSelf, também é uma brincadeira, feita para mostrar como atributos privados não são uma prioridade e que em Python, se você estiver realmente disposto, é possível restringir o acesso cada vez mais e mais (e aqui nem foi muito longe), mas se o usuário quer mesmo alterar, por que impedir?
Crédito ao João S. O. Bueno pela idéia original e algumas dicas. Ele também encontrou algumas maneiras de quebrar (que não mencionarei aqui), e para cada uma delas até encontrei uma maneira de proteger, mas acho que isso já basta para ilustrar a idéia...
1 #!/usr/bin/env python
2 # encoding: utf-8
3
4 import inspect
5 import sys
6 import types
7 import unittest
8
9
10 class PrivateAttributeError(AttributeError):
11 pass
12
13
14 def _check_caller(obj, level):
15 frame = sys._getframe(level)
16 cls = type(obj)
17
18 values = frame.f_locals.values()
19 if obj not in values:
20 return False
21
22 funcs = [func for func in cls.__dict__.values() if isinstance(func, types.FunctionType)]
23
24 for func in funcs:
25 code1 = func.func_code
26 code2 = frame.f_code
27 if code1 is code2:
28 break
29 else:
30 return False
31 return True
32
33
34 class Private(object):
35 pass
36
37
38 class PrivateAttribute(Private):
39 def __init__(self, name):
40 self.name = name
41
42 def __get__(self, obj, cls=None):
43 if not _check_caller(obj, 2):
44 raise PrivateAttributeError("can only be used inside a method")
45 try:
46 value = obj.__privdict__[self.name]
47 except KeyError:
48 raise AttributeError("Private attribute '%s' not set"%self.name)
49 return value
50
51 def __set__(self, obj, value):
52 if not _check_caller(obj, 2):
53 raise PrivateAttributeError("can only be used inside a method")
54 obj.__privdict__[self.name] = value
55
56 def __delete__(self, obj):
57 if not _check_caller(obj, 2):
58 raise PrivateAttributeError("can only be used inside a method")
59 try:
60 del obj.__privdict__[self.name]
61 except KeyError:
62 raise AttributeError("Private attribute '%s' not set"%self.name)
63
64
65 class PrivateMethod(Private):
66 def __init__(self, func):
67 self.func = func
68
69 def __get__(self, obj, cls=None):
70 if not _check_caller(obj, 2):
71 raise PrivateAttributeError("can only be used inside a method")
72 return types.MethodType(self.func, obj, cls)
73
74 def __set__(self, obj, value):
75 raise RuntimeError("You can't reasign a private method")
76
77 def __delete__(self, obj):
78 raise RuntimeError("You can't reasign a private method")
79
80
81 class PrivateDict(object):
82 __slots__ = ['owner', '__privdict__']
83 def __init__(self, owner):
84 self.owner = owner
85 self.__privdict__ = {}
86
87 def __setitem__(self, key, value):
88 if not _check_caller(self.owner, 3):
89 raise PrivateAttributeError("Are you trying to break this?")
90 self.__privdict__[key] = value
91
92 def __getitem__(self, key):
93 if not _check_caller(self.owner, 3):
94 raise PrivateAttributeError("Are you trying to break this?")
95 return self.__privdict__[key]
96
97 def __getattribute__(self, attr):
98 if not _check_caller(self, 2):
99 raise PrivateAttributeError("Are you trying to break this?")
100 return super(PrivateDict, self).__getattribute__(attr)
101
102 def __setattribute__(self, attr):
103 if not _check_caller(self, 2):
104 raise PrivateAttributeError("Are you trying to break this?")
105 return super(PrivateDict, self).__getattribute__(attr)
106
107
108 class MetaEnablePrivate(type):
109 def __call__(cls, *args, **kwds):
110 new = cls.__new__(cls, *args, **kwds)
111 new.__privdict__ = PrivateDict(new)
112 cls.__init__(new, *args, **kwds)
113 return new
114
115
116 def private(*args):
117 attrs = dict((name, PrivateAttribute(name)) for name in args)
118
119 def _metaclass(name, bases, dict):
120 dict.update(attrs)
121 return MetaEnablePrivate(name, bases, dict)
122
123 return _metaclass
124
125
126 # Um testezinho...
127 class Test(object):
128 __metaclass__ = private('foo', 'bar')
129
130 def __init__(self, name):
131 self.foo = None
132 self.bar = None
133
134 self.name = name
135 self.wow = None
136
137 def set_foo(self, value):
138 self.foo = value
139 self.bar = "and I am new bar in %s"%self.name
140
141 def get_foo(self):
142 return self.foo
143
144 @PrivateMethod
145 def meth(self):
146 return 'I am supposed to be a private method'
147
148 def call_meth(self):
149 return self.meth()
150
151
152 class TestPrivateAttributes(unittest.TestCase):
153 def setUp(self):
154 self.obja = Test('a')
155 self.objb = Test('b')
156 self.obja.set_foo('I am foo in a')
157 self.objb.set_foo('I am foo in b')
158
159 def testGettersSetters(self):
160 # get original
161 self.assertEqual(self.obja.get_foo(), 'I am foo in a')
162 self.assertEqual(self.objb.get_foo(), 'I am foo in b')
163 # set
164 self.obja.set_foo('I am new foo in a')
165 self.objb.set_foo('I am new foo in b')
166 # check if it really got set
167 self.assertEqual(self.obja.get_foo(), 'I am new foo in a')
168 self.assertEqual(self.objb.get_foo(), 'I am new foo in b')
169
170 def testBreakDirectGet(self):
171 self.assertRaises(PrivateAttributeError, getattr, self.obja, 'foo')
172 self.assertRaises(PrivateAttributeError, getattr, self.obja, 'bar')
173 self.assertEqual(self.obja.wow, None)
174 self.assertRaises(PrivateAttributeError, getattr, self.objb, 'foo')
175 self.assertRaises(PrivateAttributeError, getattr, self.objb, 'bar')
176 self.assertEqual(self.objb.wow, None)
177
178 def testBreakDirectSet(self):
179 self.assertRaises(PrivateAttributeError, setattr, self.obja, 'foo', '')
180 self.assertRaises(PrivateAttributeError, setattr, self.obja, 'bar', '')
181 self.obja.wow = ''
182 self.assertRaises(PrivateAttributeError, setattr, self.objb, 'foo', '')
183 self.assertRaises(PrivateAttributeError, setattr, self.objb, 'bar', '')
184 self.objb.wow = ''
185
186 def testWithObjectGetattribute(self):
187 self.assertRaises(PrivateAttributeError, object.__getattribute__,
188 self.obja, 'foo')
189 self.assertRaises(PrivateAttributeError, object.__getattribute__,
190 self.obja, 'bar')
191 self.assertRaises(PrivateAttributeError, object.__getattribute__,
192 self.objb, 'foo')
193 self.assertRaises(PrivateAttributeError, object.__getattribute__,
194 self.objb, 'bar')
195
196 def testWithObjectSetattr(self):
197 self.assertRaises(PrivateAttributeError, object.__setattr__,
198 self.obja, 'foo', '')
199 self.assertRaises(PrivateAttributeError, object.__setattr__,
200 self.obja, 'bar', '')
201 self.assertRaises(PrivateAttributeError, object.__setattr__,
202 self.objb, 'foo', '')
203 self.assertRaises(PrivateAttributeError, object.__setattr__,
204 self.objb, 'bar', '')
205
206 def testWithObjectPrivdict(self):
207 self.assertRaises(PrivateAttributeError, lambda :self.obja.__privdict__['foo'])
208 self.assertRaises(PrivateAttributeError, lambda :self.obja.__privdict__['bar'])
209 self.assertRaises(PrivateAttributeError, lambda :self.objb.__privdict__['foo'])
210 self.assertRaises(PrivateAttributeError, lambda :self.objb.__privdict__['bar'])
211
212
213
214 if __name__ == '__main__':
215 unittest.main()
216