meta classes

Discussion in 'Python' started by Carlo v. Dango, Sep 30, 2003.

  1. 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..


    ---
     
    Carlo v. Dango, Sep 30, 2003
    #1
    1. Advertising

  2. Carlo v. Dango

    anton muhin Guest

    Carlo v. Dango wrote:
    > 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..
    >
    >
    > ---
    >


    Really simple example:

    class Hooker(object):
    class __metaclass__(type):
    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.iteritems():
    if not n.startswith('__'):
    new_members[n] = make_wrapper(f)
    else:
    new_members[n] = f

    return type.__new__(cls, name, bases, new_members)

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

    c = Child()

    c.foo()


    hth,
    anton.
     
    anton muhin, Sep 30, 2003
    #2
    1. Advertising

  3. Carlo v. Dango

    Nicodemus Guest

    Carlo v. Dango wrote:

    > 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..



    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:

    >>> import action
    >>> class C:

    .... def foo(self, arg):
    .... print 'C.foo(%s)' % arg
    ....
    >>> c = C()
    >>> def callback(arg):

    .... print 'callback(%s)' % arg
    ....
    >>> action.after(c.foo, callback) # will call callback whenever c.foo

    is called
    >>> c.foo(10)

    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.append(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.remove(callback)
    except ValueError: pass
    try:
    method.after.remove(callback)
    except ValueError: pass
    try:
    method.result.remove(callback)
    except ValueError: pass


    #==============================================================================
    # Internals
    #==============================================================================
    def _MakeWrapper(method):
    '''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(method)
    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(FromNames)


    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.append((sender, callback))


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


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


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


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


    def remove_sender(self, sender):
    for index in xrange(len(self._actions)-1, -1, -1):
    s, c = self._actions[index]
    if s == sender:
    remove(s, c)
    self._actions.pop(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.pop(index)


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

    class ActionTest(unittest.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_count = 0
    self.before_count = 0

    def after(*args):
    self.after_called = args
    self.after_count += 1
    self.after = after

    def before(*args):
    self.before_called = args
    self.before_count += 1
    self.before = before

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


    def testClassOverride(self):
    before(self.C.foo, self.before)
    after(self.C.foo, self.after)
    result(self.C.foo, self.result)

    self.a.foo(1)
    self.assertEqual(self.foo_called, (self.a, 1))
    self.assertEqual(self.after_called, (self.a, 1))
    self.assertEqual(self.after_count, 1)
    self.assertEqual(self.before_called, (self.a, 1))
    self.assertEqual(self.before_count, 1)
    self.assertEqual(self.result_arg, 1)

    self.b.foo(2)
    self.assertEqual(self.foo_called, (self.b, 2))
    self.assertEqual(self.after_called, (self.b, 2))
    self.assertEqual(self.after_count, 2)
    self.assertEqual(self.before_called, (self.b, 2))
    self.assertEqual(self.before_count, 2)
    self.assertEqual(self.result_arg, 2)

    remove(self.C.foo, self.before)

    self.a.foo(3)
    self.assertEqual(self.foo_called, (self.a, 3))
    self.assertEqual(self.after_called, (self.a, 3))
    self.assertEqual(self.after_count, 3)
    self.assertEqual(self.before_called, (self.b, 2))
    self.assertEqual(self.before_count, 2)
    self.assertEqual(self.result_arg, 3)


    def testInstanceOverride(self):
    before(self.a.foo, self.before)
    after(self.a.foo, self.after)
    result(self.a.foo, self.result)

    self.a.foo(1)
    self.assertEqual(self.foo_called, (self.a, 1))
    self.assertEqual(self.after_called, (1,))
    self.assertEqual(self.before_called, (1,))
    self.assertEqual(self.after_count, 1)
    self.assertEqual(self.before_count, 1)
    self.assertEqual(self.result_arg, 1)

    self.b.foo(2)
    self.assertEqual(self.foo_called, (self.b, 2))
    self.assertEqual(self.after_called, (1,))
    self.assertEqual(self.before_called, (1,))
    self.assertEqual(self.after_count, 1)
    self.assertEqual(self.before_count, 1)
    self.assertEqual(self.result_arg, 1)

    remove(self.a.foo, self.before)
    self.a.foo(2)
    self.assertEqual(self.foo_called, (self.a, 2))
    self.assertEqual(self.after_called, (2,))
    self.assertEqual(self.before_called, (1,))
    self.assertEqual(self.after_count, 2)
    self.assertEqual(self.before_count, 1)
    self.assertEqual(self.result_arg, 2)

    before(self.a.foo, self.before, self.before)
    remove(self.a.foo, self.result)
    self.a.foo(5)
    self.assertEqual(self.before_called, (5,))
    self.assertEqual(self.before_count, 2)
    self.assertEqual(self.result_arg, 2)


    def testActionProperty(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_called = x
    def afterx(x):
    self.afterx_called = x

    c = C()
    before(c.setx, beforex)
    after(c.setx, afterx)
    c.x = 2
    self.assertEqual(c.x, 2)
    self.assertEqual(self.afterx_called, 2)
    self.assertEqual(self.beforex_called, 2)

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

    c = C()
    before(c.setx, beforex)
    after(c.setx, afterx)
    c.x = 2
    self.assertEqual(c.x, 2)
    self.assertEqual(self.afterx_called, 2)
    self.assertEqual(self.beforex_called, 2)

    unittest.main()
     
    Nicodemus, Oct 6, 2003
    #3
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Nym Pseudo

    META NAME and META HTTP-EQUIV

    Nym Pseudo, Sep 26, 2003, in forum: HTML
    Replies:
    1
    Views:
    573
    =?iso-8859-1?Q?brucie?=
    Sep 26, 2003
  2. Ilias Lazaridis
    Replies:
    68
    Views:
    633
    David A. Black
    May 11, 2005
  3. Duane Johnson

    Meta methods to govern meta data?

    Duane Johnson, Oct 25, 2005, in forum: Ruby
    Replies:
    6
    Views:
    250
    Adam Sanderson
    Oct 28, 2005
  4. Erik Veenstra

    Meta-Meta-Programming

    Erik Veenstra, Feb 7, 2006, in forum: Ruby
    Replies:
    29
    Views:
    412
    Erik Veenstra
    Feb 8, 2006
  5. Erik Veenstra

    Meta-Meta-Programming, revisited

    Erik Veenstra, Jul 21, 2006, in forum: Ruby
    Replies:
    21
    Views:
    460
    Erik Veenstra
    Jul 25, 2006
Loading...

Share This Page