开发者

python: subclass a metaclass

For putting methods of various classes into a global registry I'm using a decorator with a metaclass. The decorator tags, the metaclass puts the function in the registry:

class ExposedMethod (object):
    def __init__(self, decoratedFunction):
        self._decoratedFunction = decoratedFunction

    def __call__(__self,*__args,**__kw):
        return __self._decoratedFunction(*__args,**__kw)

class ExposedMethodDecoratorMetaclass(type):
    def __new__(mcs, name, bases, dct):
        for obj_name, obj in dct.iteritems():
            if isinstance(obj, ExposedMethod):
                WorkerFunctionRegistry.addWorkerToWorkerFunction(obj_name, name)
        return type.__new__(mcs, name, bases, dct)

class MyClass (object):
    __metaclass__ = DiscoveryExposedMethodDecoratorMetaclass

    @ExposeDiscov开发者_运维知识库eryMethod
    def myCoolExposedMethod (self):
        pass

I've now came to the point where two function registries are needed. The first thought was to subclass the metaclass and put the other registry in. For that the new method has simply to be rewritten.

Since rewriting means redundant code this is not what I really want. So, it would be nice if anyone could name a way how to put an attribute inside of the metaclass which is able to be read when new is executed. With that the right registry could be put in without having to rewrite new.


Your ExposedMethod instances do not behave as normal instance methods but rather like static methods -- the fact that you're giving one of them a self argument hints that you're not aware of that. You may need to add a __get__ method to the ExposedMethod class to make it a descriptor, just like function objects are -- see here for more on descriptors.

But there is a much simpler way, since functions can have attributes...:

def ExposedMethod(registry=None):
    def decorate(f):
        f.registry = registry
        return f
    return decorate

and in a class decorator (simpler than a metaclass! requires Python 2.6 or better -- in 2.5 or earlier you'll need to stick w/the metaclass or explicitly call this after the class statement, though the first part of the answer and the functionality of the code below are still perfectly fine):

def RegisterExposedMethods(cls):
    for name, f in vars(cls).iteritems():
        if not hasattr(f, 'registry'): continue
        registry = f.registry
        if registry is None:
            registry = cls.registry
        registry.register(name, cls.__name__)
    return cls

So you can do:

@RegisterExposedMethods
class MyClass (object):

    @ExposeMethod(WorkerFunctionRegistry)
    def myCoolExposedMethod (self):
        pass

and the like. This is easily extended to allowing an exposed method to have several registries, get the default registry elsewhere than from the class (it could be in the class decorator, for example, if that works better for you) and avoids getting enmeshed with metaclasses without losing any functionality. Indeed that's exactly why class decorators were introduced in Python 2.6: they can take the place of 90% or so of practical uses of metaclasses and are much simpler than custom metaclasses.


You can use a class attribute to point to the registry you want to use in the specialized metaclasses, e.g. :

class ExposedMethodDecoratorMetaclassBase(type):

    registry = None

    def __new__(mcs, name, bases, dct):
        for obj_name, obj in dct.items():
            if isinstance(obj, ExposedMethod):
                mcs.registry.register(obj_name, name)
        return type.__new__(mcs, name, bases, dct)


class WorkerExposedMethodDecoratorMetaclass(ExposedMethodDecoratorMetaclassBase):

    registry = WorkerFunctionRegistry


class RetiredExposedMethodDecoratorMetaclass(ExposedMethodDecoratorMetaclassBase):

    registry = RetiredFunctionRegistry


Thank you both for your answers. Both helped alot to find a proper way for my request.

My final solution to the problem is the following:

def ExposedMethod(decoratedFunction):
    decoratedFunction.isExposed = True
    return decoratedFunction

class RegisterExposedMethods (object):
    def __init__(self, decoratedClass, registry):
        self._decoratedClass = decoratedClass
        for name, f in vars(self._decoratedClass).iteritems():
            if hasattr(f, "isExposed"):
                registry.addComponentClassToComponentFunction(name, self._decoratedClass.__name__)

        # cloak us as the original class
        self.__class__.__name__ = decoratedClass.__name__

    def __call__(self,*__args,**__kw):
        return self._decoratedClass(*__args,**__kw)

    def __getattr__(self, name):
        return getattr(self._decoratedClass, name)

On a Class I wish to expose methods from I do the following:

@RegisterExposedMethods
class MyClass (object):
    @ExposedMethod
    def myCoolExposedMethod (self):
        pass

The class decorator is now very easy to be subclassed. Here is an example:

class DiscoveryRegisterExposedMethods (RegisterExposedMethods):
    def __init__(self, decoratedClass):
        RegisterExposedMethods.__init__(self,
                                        decoratedClass,
                                        DiscoveryFunctionRegistry())

With that the comment of Alex

Your ExposedMethod instances do not behave as normal instance methods ...

is no longer true, since the method is simply tagged and not wrapped.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜