PageRenderTime 198ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 0ms

/experimental/metaclass.py

https://github.com/e-loue/pyke
Python | 321 lines | 212 code | 44 blank | 65 comment | 9 complexity | 4caf85802dc9b1a435f04639b47d8afd MD5 | raw file
  1. # metaclass.py
  2. from pyke.unique import unique
  3. '''
  4. this metaclass is intended to be used by deriving from tracked_object as base class
  5. pros:
  6. - probably works with IronPython or Jython
  7. - easier to understand
  8. cons:
  9. - __setattr__ defined by classes poses problems
  10. '''
  11. class metaclass_option1(type): # this _must_ be derived from 'type'!
  12. _ignore_setattr = False
  13. def __init__(self, name, bases, dict):
  14. # This gets called when new derived classes are created.
  15. #
  16. # We don't need to define an __init__ method here, but I was just
  17. # curious about how this thing works...
  18. print "metaclass: name", name, ", bases", bases, \
  19. ", dict keys", tuple(sorted(dict.keys()))
  20. super(metaclass_option1, self).__init__(name, bases, dict)
  21. def __call__(self, *args, **kws):
  22. # This gets called when new instances are created (using the class as
  23. # a function).
  24. obj = super(metaclass_option1, self).__call__(*args, **kws)
  25. del obj._ignore_setattr
  26. print "add instance", obj, "to", self.knowledge_base
  27. return obj
  28. '''
  29. this metaclass requires the __metaclass__ = metaclass_option2 attribute of
  30. classes to be used with the object knowledge base of pyke
  31. pros:
  32. - solves the problem of classes defining their own __setattr__ method
  33. - does not require any multiple inheritance
  34. cons:
  35. - hard to understand
  36. - possibly does not work with IronPython or Jython
  37. '''
  38. class metaclass_option2(type): # this _must_ be derived from 'type'!
  39. def __new__(mcl, name, bases, clsdict):
  40. print "metaclass_option2.__new__: class dict before __new__: name", name, ", bases", bases, \
  41. ", dict keys", tuple(clsdict.keys()), ", dict values", tuple(clsdict.values())
  42. def __setattr__(self, attr, value):
  43. # This gets called when any attribute is changed. We would need to
  44. # figure out how to ignore attribute setting by the __init__
  45. # function...
  46. #
  47. # Also the check to see if the attribute has actually changed by doing
  48. # a '!=' check could theoretically lead to problems. For example this
  49. # would fail to change the attribute to another value that wasn't
  50. # identical to the first, but '==' to it: for example, 4 and 4.0.
  51. if self.__instance__.get(self, False) :
  52. if getattr(self, attr) != value:
  53. print "metaclass.__new__: notify knowledge base", \
  54. "of attribute change: (%s, %s, %s)" % (self, attr, value)
  55. if self.__cls__setattr__ != None:
  56. self.__cls__setattr__(attr, value)
  57. else:
  58. super(self.__class__, self).__setattr__(attr, value)
  59. else:
  60. # does not work to call super.__setattr__
  61. #super(self.__class__, self).__setattr__(attr, value)
  62. #
  63. self.__dict__[attr] = value
  64. def __getattr__(self, name):
  65. return self.__dict__[name]
  66. cls__setattr__ = None
  67. if clsdict.get('__setattr__', None) != None:
  68. cls__setattr__ = clsdict['__setattr__']
  69. clsdict['__setattr__'] = __setattr__
  70. clsdict['__getattr__'] = __getattr__
  71. clsdict['__cls__setattr__'] = cls__setattr__
  72. clsdict['__instance__'] = {}
  73. print "metaclass_option2.__new__: class dict after __new__: name", name, ", bases", bases, \
  74. ", dict keys", tuple(sorted(clsdict.keys())), ", dict values", tuple(clsdict.values())
  75. return super(metaclass_option2, mcl).__new__(mcl, name, bases, clsdict)
  76. '''
  77. def __init__(cls, name, bases, clsdict):
  78. # This gets called when new derived classes are created.
  79. #
  80. # We don't need to define an __init__ method here, but I was just
  81. # curious about how this thing works...
  82. super(metaclass_option2, cls).__init__(name, bases, clsdict)
  83. print "class dict after __init__: name", name, ", bases", bases, \
  84. ", dict keys", tuple(sorted(clsdict.keys()))
  85. # does not work to create __instance class member here
  86. #clsdict['__instance__'] = {}
  87. '''
  88. def __call__(cls, *args, **kws):
  89. # This gets called when new instances are created (using the class as
  90. # a function).
  91. obj = super(metaclass_option2, cls).__call__(*args, **kws)
  92. obj.__instance__[obj] = True
  93. print "add instance of class", cls.__name__, "to knowledge base"
  94. return obj
  95. class tracked_object(object):
  96. r'''
  97. All classes to be tracked by an object base would be derived from this
  98. one:
  99. >>> class foo(tracked_object):
  100. ... def __init__(self, arg):
  101. ... super(foo, self).__init__()
  102. ... print "foo.__init__:", arg
  103. ... self.x = arg # should be ignored
  104. ... # doctest: +NORMALIZE_WHITESPACE
  105. metaclass: name foo , bases (<class 'experimental.metaclass.tracked_object'>,) ,
  106. dict keys ('__init__', '__module__')
  107. And we can keep deriving classes:
  108. >>> class bar(foo):
  109. ... def __init__(self, arg1, arg2):
  110. ... super(bar, self).__init__(arg1)
  111. ... print "bar.__init__:", arg1, arg2
  112. ... self.y = arg2 # should be ignored
  113. ... # doctest: +NORMALIZE_WHITESPACE
  114. metaclass: name bar , bases (<class 'experimental.metaclass.foo'>,) ,
  115. dict keys ('__init__', '__module__')
  116. We can't do the next step directly in the class definition because the
  117. knowledge_engine.engine hasn't been created yet and so the object
  118. bases don't exist at that point in time.
  119. So this simulates adding the knowledge_base to the class later, after
  120. the knowledge_engine.engine and object bases have been created.
  121. >>> foo.knowledge_base = 'foo base'
  122. >>> bar.knowledge_base = 'bar base'
  123. And now we create some instances (shouldn't see any attribute change
  124. notifications here!):
  125. >>> f = foo(44) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
  126. tracked_object.__setattr__ called on object
  127. <experimental.metaclass.foo object at 0x...>
  128. with property _ignore_setattr and value True
  129. tracked_object.__setattr__ called on object
  130. <experimental.metaclass.foo object at 0x...>
  131. with property knowledgebase and value None
  132. foo.__init__: 44
  133. tracked_object.__setattr__ called on object
  134. <experimental.metaclass.foo object at 0x...>
  135. with property x and value 44
  136. add instance <experimental.metaclass.foo object at 0x...> to foo base
  137. >>> b = bar(55, 66) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
  138. tracked_object.__setattr__ called on object
  139. <experimental.metaclass.bar object at 0x...>
  140. with property _ignore_setattr and value True
  141. tracked_object.__setattr__ called on object
  142. <experimental.metaclass.bar object at 0x...>
  143. with property knowledgebase and value None
  144. foo.__init__: 55
  145. tracked_object.__setattr__ called on object
  146. <experimental.metaclass.bar object at 0x...>
  147. with property x and value 55
  148. bar.__init__: 55 66
  149. tracked_object.__setattr__ called on object
  150. <experimental.metaclass.bar object at 0x...>
  151. with property y and value 66
  152. add instance <experimental.metaclass.bar object at 0x...> to bar base
  153. And modify some attributes:
  154. >>> f.x = 'y' # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
  155. tracked_object.__setattr__ called on object
  156. <experimental.metaclass.foo object at 0x...>
  157. with property x and value y
  158. tracked_object.__setattr__: notify foo base of attribute change:
  159. (<experimental.metaclass.foo object at 0x...>, x, y)
  160. >>> b.y = 'z' # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
  161. tracked_object.__setattr__ called on object
  162. <experimental.metaclass.bar object at 0x...>
  163. with property y and value z
  164. tracked_object.__setattr__: notify bar base of attribute change:
  165. (<experimental.metaclass.bar object at 0x...>, y, z)
  166. >>> b.y = 'z' # should be ignored
  167. ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
  168. tracked_object.__setattr__ called on object
  169. <experimental.metaclass.bar object at 0x...>
  170. with property y and value z
  171. >>> b.z = "wasn't set" # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
  172. tracked_object.__setattr__ called on object
  173. <experimental.metaclass.bar object at 0x...>
  174. with property z and value wasn't set
  175. tracked_object.__setattr__: notify bar base of attribute change:
  176. (<experimental.metaclass.bar object at 0x...>, z, wasn't set)
  177. '''
  178. __metaclass__ = metaclass_option1
  179. _not_bound = unique('_not_bound') # a value that should != any other value!
  180. def __init__(self):
  181. self._ignore_setattr = True
  182. self.knowledgebase = None
  183. def __setattr__(self, attr, value):
  184. # This gets called when any attribute is changed. We would need to
  185. # figure out how to ignore attribute setting by the __init__
  186. # function...
  187. #
  188. # Also the check to see if the attribute has actually changed by doing
  189. # a '!=' check could theoretically lead to problems. For example this
  190. # would fail to change the attribute to another value that wasn't
  191. # identical to the first, but '==' to it: for example, 4 and 4.0.
  192. print "tracked_object.__setattr__ called on object %s with property %s and value %s" % (self, attr, value)
  193. if getattr(self, attr, self._not_bound) != value:
  194. super(tracked_object, self).__setattr__(attr, value)
  195. if not hasattr(self, '_ignore_setattr'):
  196. print "tracked_object.__setattr__: notify", self.knowledge_base, \
  197. "of attribute change: (%s, %s, %s)" % (self, attr, value)
  198. ''' tracked_object and foo_tracked use metaclass_option1
  199. '''
  200. class foo_tracked(tracked_object):
  201. def __init__(self, arg):
  202. super(foo_tracked, self).__init__()
  203. self.prop = arg
  204. ''' the following classes use metaclass_option2
  205. '''
  206. class foo_base(object):
  207. def __setattr__(self, attr, value):
  208. print "foo_base.__setattr__ called on object %s with property %s and value %s" % (self, attr, value)
  209. class foo_attribute_base(foo_base):
  210. __metaclass__ = metaclass_option2
  211. def __init__(self, arg):
  212. super(foo_attribute_base, self).__init__()
  213. self.prop = arg
  214. class foo_attribute(object):
  215. __metaclass__ = metaclass_option2
  216. def __init__(self, arg):
  217. super(foo_attribute, self).__init__()
  218. self.prop = arg
  219. def __setattr__(self, attr, value):
  220. print "foo_attribute.__setattr__ called on object %s with property %s and value %s" % (self, attr, value)
  221. class foo(object):
  222. __metaclass__ = metaclass_option2
  223. def __init__(self, arg):
  224. super(foo, self).__init__()
  225. self.prop = arg
  226. #self.knowledge_base = "foo"
  227. def foo_method(self):
  228. print "foo_method called"
  229. def test_foo_option2():
  230. f1 = foo(1) # should add instance to knowledge base
  231. f1.prop = 2 # should notify knowledge base of property change
  232. f2 = foo("egon") # should add instance to knowledge base
  233. f2.prop = "ralf" # should notify knowledge base of property change
  234. f3 = foo_attribute(3)
  235. f3.prop = 4
  236. f4 = foo_attribute("karin")
  237. f4.prop = "sabine"
  238. f5 = foo_attribute_base(5)
  239. f5.prop = 6
  240. f6 = foo_attribute_base("sebastian")
  241. f6.prop = "philipp"
  242. def test_foo_option1():
  243. import sys
  244. import doctest
  245. sys.exit(doctest.testmod(optionflags = doctest.ELLIPSIS
  246. | doctest.NORMALIZE_WHITESPACE)
  247. [0])
  248. if __name__ == "__main__":
  249. #test_foo_option1()
  250. test_foo_option2()