creating classes with mix-ins

samwyse samwyse at gmail.com
Tue May 12 07:45:41 EDT 2009


On May 11, 9:01 pm, Carl Banks <pavlovevide... at gmail.com> wrote:
> On May 11, 11:16 am, samwyse <samw... at gmail.com> wrote:
>
> > Should I use a class decorator, or a metaclass?
>
> Here's the thing: unless you have advance knowledge of the methods
> defined by self.blog, you can't get the attr_list at class definition
> time, which means neither the metaclass nor the decorator would be a
> good approach.  If that's the case, you should define newPost,
> editPost, and whatever other methods of self.blog as ordinary
> attributes of self, within the init function.  boilerplate would be
> the same except you would pass self to it and allow template to use it
> from its nested scope (it would no longer be an instance method since
> it's an ordinary attribute).
>
> If you do know what the methods of self.blog will be, then that's
> where you get attr_list from.  So, for instance, if blogHandler always
> returns an object of type Blog, then you could inspect Blog's type
> dict to see what methods are defined in it; in fact you probably want
> to check the whole MRO for Blog, like this (untested):
>
> attr_list = []
> for cls in Blog.__mro__:
>     for value in cls.__dict__:
>         if is_wrapped_method(value):
>             attr_list.append(value)
>
> A metaclass is probably overkill to assign the wrapped blog methods.
> I probably wouldn't even bother with the decorator, and just write the
> loop after the class definition.  Then you can use MetaBlog directly
> for klass.
>
> class MetaBlog(object):
>     ...
>
> for what in attr_list:
>     setattr(MetaBlog, what, boilerplate(what))
>
> If it were the kind of thing I found myself doing often I'd refactor
> into a decorator.

Unfortunately, 'boilerplate()' uses the handlers that I provide when
MetaBlog is instantiated.  I tried the following, but it didn't work
(for reasons that were obvious in retrospect).

def instantiate_template(m_name, instance):
    isAuthorised = instance.security.isAuthorised
    method_to_wrap = getattr(instance.blog, m_name)
    def template(self, which, username, password, *args):
        if not isAuthorised(username, password, which, m_name):
            raise Exception('Unauthorised access')
        return method_to_wrap(which, *args)
    template.__name__ = method_to_wrap.__name__
    template.__doc__ = method_to_wrap.__doc__
    return template

class MetaWeblog(object):
    def __init__(self,
                 securityHandler=SimpleSecurityHandler,
                 blogHandler=SimpleBlogHandler):
        self.security = securityHandler()
        self.blog = blogHandler()
        # from http://www.xmlrpc.com/metaWeblogApi
        m_prefix = 'metaWeblog.'
        m_list = ('newPost', 'editPost', 'getPost', 'newMediaObject',
                  'getCategories', 'getRecentPosts', )

        # Here's where things fell apart
        for m_name in m_list:
            dotted_name = m_prefix + m_name
            method = instantiate_template(m_name, self)
            setattr(self, dotted_name, method)

So, I replaced that last for-loop with this:

        # Since we're about to monkey-patch the class, we should
        # make certain that all instances use the same handlers.
        handlers = (self.security, self.blog)
        try:
            assert getattr(self.__class__, '_handlers') == handlers
        except AttributeError:
            for m_name in m_list:
                dotted_name = m_prefix + m_name
                method = instantiate_template(m_name, self)
                setattr(self.__class__, dotted_name, method)
            setattr(self.__class__, '_handlers', handlers)

This is good enough for now, since I can't conceive of a reason why
MetaBlog would be instantiated more than once.  If it were, on the
other hand, it would probably be because you wanted to use different
handlers.  In that case, I think I'd want to use a class factory,
something like this:
    server.register_instance(
        MetaWeblogFactory(securityHandler, blogHandler)()
    )

Anyway, thanks for getting me over a conceptual hump.



More information about the Python-list mailing list