Work at SourceForge, help us to make it a better place! We have an immediate need for a Support Technician in our San Francisco or Denver office.

Close

[23a00c]: pyke / metaclass.py Maximize Restore History

Download this file

metaclass.py    286 lines (214 with data), 10.3 kB

  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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# metaclass.py
from pyke.unique import unique
'''
this metaclass is intended to be used by deriving from tracked_object as base class
pros:
- probably works with IronPython or Jython
- easier to understand
cons:
- __setattr__ defined by classes poses problems
'''
class metaclass_option1(type): # this _must_ be derived from 'type'!
_ignore_setattr = False
def __init__(self, name, bases, dict):
# This gets called when new derived classes are created.
#
# We don't need to define an __init__ method here, but I was just
# curious about how this thing works...
print "metaclass: name", name, ", bases", bases, \
", dict keys", tuple(sorted(dict.keys()))
super(metaclass_option1, self).__init__(name, bases, dict)
def __call__(self, *args, **kws):
# This gets called when new instances are created (using the class as
# a function).
obj = super(metaclass_option1, self).__call__(*args, **kws)
del obj._ignore_setattr
print "add instance", obj, "to", self.knowledge_base
return obj
'''
this metaclass requires the __metaclass__ = metaclass_option2 attribute of
classes to be used with the object knowledge base of pyke
pros:
- solves the problem of classes defining their own __setattr__ method
- does not require any multiple inheritance
cons:
- hard to understand
- possibly does not work with IronPython or Jython
'''
class metaclass_option2(type): # this _must_ be derived from 'type'!
def __new__(mcl, name, bases, clsdict):
print "metaclass_option2.__new__: class dict before __new__: name", name, ", bases", bases, \
", dict keys", tuple(clsdict.keys()), ", dict values", tuple(clsdict.values())
def __setattr__(self, attr, value):
# This gets called when any attribute is changed. We would need to
# figure out how to ignore attribute setting by the __init__
# function...
#
# Also the check to see if the attribute has actually changed by doing
# a '!=' check could theoretically lead to problems. For example this
# would fail to change the attribute to another value that wasn't
# identical to the first, but '==' to it: for example, 4 and 4.0.
if self.__instance__.get(self, False) :
if getattr(self, attr) != value:
print "metaclass.__new__: notify knowledge base", \
"of attribute change: (%s, %s, %s)" % (self, attr, value)
if self.__cls__setattr__ != None:
self.__cls__setattr__(attr, value)
else:
super(self.__class__, self).__setattr__(attr, value)
else:
# does not work to call super.__setattr__
#super(self.__class__, self).__setattr__(attr, value)
#
self.__dict__[attr] = value
def __getattr__(self, name):
return self.__dict__[name]
cls__setattr__ = None
if clsdict.get('__setattr__', None) != None:
cls__setattr__ = clsdict['__setattr__']
clsdict['__setattr__'] = __setattr__
clsdict['__getattr__'] = __getattr__
clsdict['__cls__setattr__'] = cls__setattr__
clsdict['__instance__'] = {}
print "metaclass_option2.__new__: class dict after __new__: name", name, ", bases", bases, \
", dict keys", tuple(sorted(clsdict.keys())), ", dict values", tuple(clsdict.values())
return super(metaclass_option2, mcl).__new__(mcl, name, bases, clsdict)
'''
def __init__(cls, name, bases, clsdict):
# This gets called when new derived classes are created.
#
# We don't need to define an __init__ method here, but I was just
# curious about how this thing works...
super(metaclass_option2, cls).__init__(name, bases, clsdict)
print "class dict after __init__: name", name, ", bases", bases, \
", dict keys", tuple(sorted(clsdict.keys()))
# does not work to create __instance class member here
#clsdict['__instance__'] = {}
'''
def __call__(cls, *args, **kws):
# This gets called when new instances are created (using the class as
# a function).
obj = super(metaclass_option2, cls).__call__(*args, **kws)
obj.__instance__[obj] = True
print "add instance of class", cls.__name__, "to knowledge base"
return obj
class tracked_object(object):
r'''
All classes to be tracked by an object base would be derived from this
one:
>>> class foo(tracked_object):
... def __init__(self, arg):
... super(foo, self).__init__()
... print "foo.__init__:", arg
... self.x = arg # should be ignored
metaclass: name foo , bases (<class '__main__.tracked_object'>,) ,
dict keys ('__init__', '__module__')
And we can keep deriving classes:
>>> class bar(foo):
... def __init__(self, arg1, arg2):
... super(bar, self).__init__(arg1)
... print "bar.__init__:", arg1, arg2
... self.y = arg2 # should be ignored
metaclass: name bar , bases (<class '__main__.foo'>,) ,
dict keys ('__init__', '__module__')
We can't do the next step directly in the class definition because the
knowledge_engine.engine hasn't been created yet and so the object
bases don't exist at that point in time.
So this simulates adding the knowledge_base to the class later, after
the knowledge_engine.engine and object bases have been created.
>>> foo.knowledge_base = 'foo base'
>>> bar.knowledge_base = 'bar base'
And now we create some instances (shouldn't see any attribute change
notifications here!):
>>> f = foo(44)
foo.__init__: 44
add instance <__main__.foo object at 0x...> to foo base
>>> b = bar(55, 66)
foo.__init__: 55
bar.__init__: 55 66
add instance <__main__.bar object at 0x...> to bar base
And modify some attributes:
>>> f.x = 'y'
notify foo base of attribute change:
(<__main__.foo object at 0x...>, x, y)
>>> b.y = 'z'
notify bar base of attribute change:
(<__main__.bar object at 0x...>, y, z)
>>> b.y = 'z' # should be ignored
>>> b.z = "wasn't set"
notify bar base of attribute change:
(<__main__.bar object at 0x...>, z, wasn't set)
'''
__metaclass__ = metaclass_option1
_not_bound = unique('_not_bound') # a value that should != any other value!
def __init__(self):
self._ignore_setattr = True
self.knowledgebase = None
def __setattr__(self, attr, value):
# This gets called when any attribute is changed. We would need to
# figure out how to ignore attribute setting by the __init__
# function...
#
# Also the check to see if the attribute has actually changed by doing
# a '!=' check could theoretically lead to problems. For example this
# would fail to change the attribute to another value that wasn't
# identical to the first, but '==' to it: for example, 4 and 4.0.
print "tracked_object.__setattr__ called on object %s with property %s and value %s" % (self, attr, value)
if getattr(self, attr, self._not_bound) != value:
super(tracked_object, self).__setattr__(attr, value)
if not hasattr(self, '_ignore_setattr'):
print "tracked_object.__setattr__: notify", self.knowledge_base, \
"of attribute change: (%s, %s, %s)" % (self, attr, value)
''' tracked_object and foo_tracked use metaclass_option1
'''
class foo_tracked(tracked_object):
def __init__(self, arg):
super(foo_tracked, self).__init__()
self.prop = arg
''' the following classes use metaclass_option2
'''
class foo_base(object):
def __setattr__(self, attr, value):
print "foo_base.__setattr__ called on object %s with property %s and value %s" % (self, attr, value)
class foo_attribute_base(foo_base):
__metaclass__ = metaclass_option2
def __init__(self, arg):
super(foo_attribute_base, self).__init__()
self.prop = arg
class foo_attribute(object):
__metaclass__ = metaclass_option2
def __init__(self, arg):
super(foo_attribute, self).__init__()
self.prop = arg
def __setattr__(self, attr, value):
print "foo_attribute.__setattr__ called on object %s with property %s and value %s" % (self, attr, value)
class foo(object):
__metaclass__ = metaclass_option2
def __init__(self, arg):
super(foo, self).__init__()
self.prop = arg
#self.knowledge_base = "foo"
def foo_method(self):
print "foo_method called"
def test_foo_option2():
f1 = foo(1) # should add instance to knowledge base
f1.prop = 2 # should notify knowledge base of property change
f2 = foo("egon") # should add instance to knowledge base
f2.prop = "ralf" # should notify knowledge base of property change
f3 = foo_attribute(3)
f3.prop = 4
f4 = foo_attribute("karin")
f4.prop = "sabine"
f5 = foo_attribute_base(5)
f5.prop = 6
f6 = foo_attribute_base("sebastian")
f6.prop = "philipp"
def test_foo_option1():
import sys
import doctest
sys.exit(doctest.testmod(optionflags = doctest.ELLIPSIS
| doctest.NORMALIZE_WHITESPACE)
[0])
if __name__ == "__main__":
#test_foo_option1()
test_foo_option2()