Python: 子类化一个 metaclass

2 投票
3 回答
3852 浏览
提问于 2025-04-15 22:12

为了把不同类的方法放到一个全局注册表里,我使用了一个带有 metaclass 的装饰器。这个装饰器负责标记,而 metaclass 则把函数放进注册表里:

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

    @ExposeDiscoveryMethod
    def myCoolExposedMethod (self):
        pass

现在我遇到了一个问题,需要两个函数注册表。最初的想法是通过子类化 metaclass 来把另一个注册表放进去。为此,只需要重写 new 方法。

不过,重写方法会导致代码重复,这并不是我想要的。所以,如果有人能提供一种方法,让我在 metaclass 中放一个属性,并且在执行 new 时能够读取到,那就太好了。这样我就可以在不重写 new 的情况下使用正确的注册表。

3 个回答

0

你可以使用一个类属性来指向你想在特定的元类中使用的注册表,比如:

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
1

谢谢你们的回答,真的帮了我很多,让我找到了解决问题的好方法。

我最终解决这个问题的方法如下:

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)

在一个类中,我想公开一些方法,我是这样做的:

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

现在这个类的装饰器很容易被子类化。这里有个例子:

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

这样一来,Alex 的评论

你的 ExposedMethod 实例并不像普通实例方法那样工作……

就不再成立了,因为这个方法只是被标记,而不是被包裹起来。

5

你的 ExposedMethod 实例并不像普通的方法那样工作,而更像是静态方法。你给其中一个方法传递 self 参数,这说明你可能没有意识到这一点。你可能需要在 ExposedMethod 类中添加一个 __get__ 方法,让它变成一个描述符,就像函数对象一样。想了解更多关于描述符的内容,可以查看这里

不过,有一种简单得多的方法,因为函数是可以有属性的……:

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

而在一个装饰器中(比元类简单!需要 Python 2.6 或更高版本——在 2.5 或更早的版本中,你需要继续使用元类,或者在 class 语句后显式调用这个,尽管答案的第一部分和下面代码的功能仍然完全没问题):

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

所以你可以这样做:

@RegisterExposedMethods
class MyClass (object):

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

以及类似的操作。这很容易扩展,可以让一个暴露的方法有多个注册表,默认注册表可以不来自类(比如可以在类装饰器中,如果这样对你更合适的话)并且避免与元类纠缠在一起,同时不失去任何功能。实际上,这正是 Python 2.6 引入类装饰器的原因:它们可以替代大约 90% 的元类的实际用途,而且比自定义元类简单得多

撰写回答