Python: 子类化一个 metaclass
为了把不同类的方法放到一个全局注册表里,我使用了一个带有 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 个回答
你可以使用一个类属性来指向你想在特定的元类中使用的注册表,比如:
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
谢谢你们的回答,真的帮了我很多,让我找到了解决问题的好方法。
我最终解决这个问题的方法如下:
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 实例并不像普通实例方法那样工作……
就不再成立了,因为这个方法只是被标记,而不是被包裹起来。
你的 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% 的元类的实际用途,而且比自定义元类简单得多。