meta classes

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Carlo v. Dango

    meta classes

    hello there

    I'd like to take control over the method dispatching of every subclass of a
    given python class. Currently I've redefined __getattribute_ _() so i can
    find the right instance/method to dispatch to. This works fine, albeit it
    may seem a bit hackish. Further I want to control the actual dispatching,
    so that I can execute methods before, after or instead of the actual
    method. I would prefer to do this with the least modification of the source
    due to debug'ing issues. My best idea so far is to rename every method who
    subclass the magic class X and then create new methods in place of the
    renamed ones, which looks up in a list to execute "before-methods", then
    execute the renamed method etc..

    Only I don't know how to do this. I've found some MOP code which was
    designed for python old-style objects... but this is not what I want. Can
    anyone kindly give me some pointers for help or some actual code?

    many thanks in advance..


    ---

  • anton muhin

    #2
    Re: meta classes

    Carlo v. Dango wrote:[color=blue]
    > hello there
    >
    > I'd like to take control over the method dispatching of every subclass
    > of a given python class. Currently I've redefined __getattribute_ _() so
    > i can find the right instance/method to dispatch to. This works fine,
    > albeit it may seem a bit hackish. Further I want to control the actual
    > dispatching, so that I can execute methods before, after or instead of
    > the actual method. I would prefer to do this with the least modification
    > of the source due to debug'ing issues. My best idea so far is to rename
    > every method who subclass the magic class X and then create new methods
    > in place of the renamed ones, which looks up in a list to execute
    > "before-methods", then execute the renamed method etc..
    >
    > Only I don't know how to do this. I've found some MOP code which was
    > designed for python old-style objects... but this is not what I want.
    > Can anyone kindly give me some pointers for help or some actual code?
    >
    > many thanks in advance..
    >
    >
    > ---
    >[/color]

    Really simple example:

    class Hooker(object):
    class __metaclass__(t ype):
    def __new__(cls, name, bases, members):
    def make_wrapper(f) :
    def wrapper(self):
    print "before"
    f(self)
    print "after"
    return wrapper

    new_members = {}
    for n, f in members.iterite ms():
    if not n.startswith('_ _'):
    new_members[n] = make_wrapper(f)
    else:
    new_members[n] = f

    return type.__new__(cl s, name, bases, new_members)

    class Child(Hooker):
    def foo(self):
    print "Child.foo"

    c = Child()

    c.foo()


    hth,
    anton.

    Comment

    • Nicodemus

      #3
      Re: meta classes

      Carlo v. Dango wrote:
      [color=blue]
      > hello there
      >
      > I'd like to take control over the method dispatching of every subclass
      > of a given python class. Currently I've redefined __getattribute_ _()
      > so i can find the right instance/method to dispatch to. This works
      > fine, albeit it may seem a bit hackish. Further I want to control the
      > actual dispatching, so that I can execute methods before, after or
      > instead of the actual method. I would prefer to do this with the least
      > modification of the source due to debug'ing issues. My best idea so
      > far is to rename every method who subclass the magic class X and then
      > create new methods in place of the renamed ones, which looks up in a
      > list to execute "before-methods", then execute the renamed method etc..
      >
      > Only I don't know how to do this. I've found some MOP code which was
      > designed for python old-style objects... but this is not what I want.
      > Can anyone kindly give me some pointers for help or some actual code?
      >
      > many thanks in advance..[/color]


      You don't need metaclasses at all, actually. I attached the action
      module that I created at work. You can set callbacks in any class:
      [color=blue][color=green][color=darkred]
      >>> import action
      >>> class C:[/color][/color][/color]
      .... def foo(self, arg):
      .... print 'C.foo(%s)' % arg
      ....[color=blue][color=green][color=darkred]
      >>> c = C()
      >>> def callback(arg):[/color][/color][/color]
      .... print 'callback(%s)' % arg
      ....[color=blue][color=green][color=darkred]
      >>> action.after(c. foo, callback) # will call callback whenever c.foo[/color][/color][/color]
      is called[color=blue][color=green][color=darkred]
      >>> c.foo(10)[/color][/color][/color]
      C.foo(10)
      callback(10)


      Besides after(), you have also before(). Also there's result(), where
      the callback receives as parameter the return of the given function.

      HTH,
      Nicodemus.






      '''
      Implements non-intrusive callbacks for methods.

      Note: this module doesn't work with Qt objects, and with methods that are
      attached to a property (setting a property won't trigger the callback!)
      '''

      #============== =============== =============== =============== =============== ====
      # before
      #============== =============== =============== =============== =============== ====
      def before(method, *callbacks):
      '''Registers the given callbacks to be execute before the given method is
      called, with the same arguments. The method can be eiher an unbound method
      or a bound method. If it is an unbound method, *all* instances of the class
      will generate callbacks when method is called. If it is a bound method, only
      the method of the instance will generate callbacks.
      '''
      wrapper = _Setup(method)
      for callback in callbacks:
      if callback not in wrapper.before:
      wrapper.before. append(callback )


      #============== =============== =============== =============== =============== ====
      # after
      #============== =============== =============== =============== =============== ====
      def after(method, *callbacks):
      '''Same as before, but will callback after method is executed.
      '''
      wrapper = _Setup(method)
      for callback in callbacks:
      if callback not in wrapper.after:
      wrapper.after.a ppend(callback)


      #============== =============== =============== =============== =============== ====
      # result
      #============== =============== =============== =============== =============== ====
      def result(method, *callbacks):
      '''The callbacks are called after the given method is executed, and the
      callbacks will receive the return value of the method as parameter.
      '''
      wrapper = _Setup(method)
      for callback in callbacks:
      if callback not in wrapper.result:
      wrapper.result. append(callback )


      #============== =============== =============== =============== =============== ====
      # remove
      #============== =============== =============== =============== =============== ====
      def remove(method, callback):
      '''Removes the given callback from a method previously connected using
      after or before.
      '''
      if hasattr(method, 'before') and hasattr(method, 'after'):
      try:
      method.before.r emove(callback)
      except ValueError: pass
      try:
      method.after.re move(callback)
      except ValueError: pass
      try:
      method.result.r emove(callback)
      except ValueError: pass


      #============== =============== =============== =============== =============== ====
      # Internals
      #============== =============== =============== =============== =============== ====
      def _MakeWrapper(me thod):
      '''Creates a function with two lists of callbacks, before and after.
      This function when called, will call all the "before" callbacks, call
      the original method, and then call all the "after" callbacks.
      '''

      def wrapper(*args, **kwargs):
      # call all the before callbacks
      for callback in wrapper.before:
      callback(*args, **kwargs)
      # call original method
      result = method(*args, **kwargs)
      # call all the after callbacks
      for callback in wrapper.after:
      callback(*args, **kwargs)
      # call all result callbacks
      for callback in wrapper.result:
      callback(result )
      return result

      wrapper.before = []
      wrapper.after = []
      wrapper.result = []
      return wrapper


      def _Setup(method):
      '''Generates a wrapper for the given method, or returns the method itself
      if it is already a wrapper.
      '''
      if hasattr(method, 'before') and hasattr(method, 'after'):
      # its a wrapper already
      return method
      else:
      wrapper = _MakeWrapper(me thod)
      if method.im_self is None:
      # override the class method
      target = method.im_class
      else:
      # override the instance method
      target = method.im_self
      setattr(target, method.__name__ , wrapper)
      return wrapper


      #============== =============== =============== =============== =============== ====
      # property
      #============== =============== =============== =============== =============== ====
      class property(object ):
      '''Creates an property just like a built-in property, except that it can
      be used with the actions in this module.
      '''

      def __init__(self, fget=None, fset=None, fdel=None, doc=None):
      self.fget_name = fget and fget.__name__
      self.fset_name = fset and fset.__name__
      self.fdel_name = fdel and fdet.__name__
      self.doc = doc or ''


      def FromNames(cls, fget_name, fset_name=None, fdel_name=None, doc=None):
      self = cls()
      self.fget_name = fget_name
      self.fset_name = fset_name
      self.fdel_name = fdel_name
      self.doc = doc or ''
      return self

      FromNames = classmethod(Fro mNames)


      def __get__(self, obj, objtype=None):
      if obj is None:
      return self
      if self.fget_name is None:
      raise AttributeError, "unreadable attribute"
      fget = getattr(obj, self.fget_name)
      return fget()


      def __set__(self, obj, value):
      if self.fset_name is None:
      raise AttributeError, "can't set attribute"
      fset = getattr(obj, self.fset_name)
      fset(value)


      def __delete__(self , obj):
      if self.fdel_name is None:
      raise AttributeError, "can't delete attribute"
      fdel = getattr(obj, self.fdel_name)
      fdel()


      #============== =============== =============== =============== =============== ====
      # Actions
      #============== =============== =============== =============== =============== ====
      class Actions(object) :
      '''Holds the connections created, making easy to disconnect later.
      '''

      def __init__(self):
      self._actions = []


      def before(self, sender, *callbacks):
      before(sender, *callbacks)
      for callback in callbacks:
      self._actions.a ppend((sender, callback))


      def after(self, sender, *callbacks):
      after(sender, *callbacks)
      for callback in callbacks:
      self._actions.a ppend((sender, callback))


      def result(self, sender, *callbacks):
      result(sender, *callbacks)
      for callback in callbacks:
      self._actions.a ppend((sender, callback))


      def remove(self, sender, callback):
      remove(sender, callback)
      for s, c in self._actions:
      if sender == s and callback == c:
      self._actions.r emove((sender, callback))
      break


      def remove_all(self ):
      for sender, callback in self._actions:
      remove(sender, callback)
      self._actions[:] = []


      def remove_sender(s elf, sender):
      for index in xrange(len(self ._actions)-1, -1, -1):
      s, c = self._actions[index]
      if s == sender:
      remove(s, c)
      self._actions.p op(index)


      def remove_callback (self, callback):
      for index in xrange(len(self ._actions)-1, -1, -1):
      s, c = self._actions[index]
      if c == callback:
      remove(s, c)
      self._actions.p op(index)


      #============== =============== =============== =============== =============== ====
      # tests
      #============== =============== =============== =============== =============== ====
      if __name__ == '__main__':
      import unittest

      class ActionTest(unit test.TestCase):

      def setUp(self):
      class C:
      def foo(s, arg):
      self.foo_called = (s, arg)
      return arg
      self.C = C
      self.a = C()
      self.b = C()
      self.after_coun t = 0
      self.before_cou nt = 0

      def after(*args):
      self.after_call ed = args
      self.after_coun t += 1
      self.after = after

      def before(*args):
      self.before_cal led = args
      self.before_cou nt += 1
      self.before = before

      def result(arg):
      self.result_arg = arg
      self.result = result


      def testClassOverri de(self):
      before(self.C.f oo, self.before)
      after(self.C.fo o, self.after)
      result(self.C.f oo, self.result)

      self.a.foo(1)
      self.assertEqua l(self.foo_call ed, (self.a, 1))
      self.assertEqua l(self.after_ca lled, (self.a, 1))
      self.assertEqua l(self.after_co unt, 1)
      self.assertEqua l(self.before_c alled, (self.a, 1))
      self.assertEqua l(self.before_c ount, 1)
      self.assertEqua l(self.result_a rg, 1)

      self.b.foo(2)
      self.assertEqua l(self.foo_call ed, (self.b, 2))
      self.assertEqua l(self.after_ca lled, (self.b, 2))
      self.assertEqua l(self.after_co unt, 2)
      self.assertEqua l(self.before_c alled, (self.b, 2))
      self.assertEqua l(self.before_c ount, 2)
      self.assertEqua l(self.result_a rg, 2)

      remove(self.C.f oo, self.before)

      self.a.foo(3)
      self.assertEqua l(self.foo_call ed, (self.a, 3))
      self.assertEqua l(self.after_ca lled, (self.a, 3))
      self.assertEqua l(self.after_co unt, 3)
      self.assertEqua l(self.before_c alled, (self.b, 2))
      self.assertEqua l(self.before_c ount, 2)
      self.assertEqua l(self.result_a rg, 3)


      def testInstanceOve rride(self):
      before(self.a.f oo, self.before)
      after(self.a.fo o, self.after)
      result(self.a.f oo, self.result)

      self.a.foo(1)
      self.assertEqua l(self.foo_call ed, (self.a, 1))
      self.assertEqua l(self.after_ca lled, (1,))
      self.assertEqua l(self.before_c alled, (1,))
      self.assertEqua l(self.after_co unt, 1)
      self.assertEqua l(self.before_c ount, 1)
      self.assertEqua l(self.result_a rg, 1)

      self.b.foo(2)
      self.assertEqua l(self.foo_call ed, (self.b, 2))
      self.assertEqua l(self.after_ca lled, (1,))
      self.assertEqua l(self.before_c alled, (1,))
      self.assertEqua l(self.after_co unt, 1)
      self.assertEqua l(self.before_c ount, 1)
      self.assertEqua l(self.result_a rg, 1)

      remove(self.a.f oo, self.before)
      self.a.foo(2)
      self.assertEqua l(self.foo_call ed, (self.a, 2))
      self.assertEqua l(self.after_ca lled, (2,))
      self.assertEqua l(self.before_c alled, (1,))
      self.assertEqua l(self.after_co unt, 2)
      self.assertEqua l(self.before_c ount, 1)
      self.assertEqua l(self.result_a rg, 2)

      before(self.a.f oo, self.before, self.before)
      remove(self.a.f oo, self.result)
      self.a.foo(5)
      self.assertEqua l(self.before_c alled, (5,))
      self.assertEqua l(self.before_c ount, 2)
      self.assertEqua l(self.result_a rg, 2)


      def testActionPrope rty(self):
      class C(object):
      def setx(self, x): self._x = x
      def getx(self): return self._x
      x = property(getx, setx)

      def beforex(x):
      self.beforex_ca lled = x
      def afterx(x):
      self.afterx_cal led = x

      c = C()
      before(c.setx, beforex)
      after(c.setx, afterx)
      c.x = 2
      self.assertEqua l(c.x, 2)
      self.assertEqua l(self.afterx_c alled, 2)
      self.assertEqua l(self.beforex_ called, 2)

      class C(object):
      def setx(self, x): self._x = x
      def getx(self): return self._x
      x = property.FromNa mes('getx', 'setx')

      c = C()
      before(c.setx, beforex)
      after(c.setx, afterx)
      c.x = 2
      self.assertEqua l(c.x, 2)
      self.assertEqua l(self.afterx_c alled, 2)
      self.assertEqua l(self.beforex_ called, 2)

      unittest.main()




      Comment

      Working...