元类混入还是链式调用?
可以把元类串联起来吗?
我有一个叫 Model
的类,它使用 __metaclass__=ModelBase
来处理它的命名空间字典。我打算从这个类继承,并“绑定”另一个元类,这样就不会覆盖原来的元类。
第一种方法是创建一个子类 class MyModelBase(ModelBase)
:
MyModel(Model):
__metaclass__ = MyModelBase # inherits from `ModelBase`
但是有没有可能像混入那样直接把它们串联起来,而不需要显式地创建子类呢?比如说:
class MyModel(Model):
__metaclass__ = (MyMixin, super(Model).__metaclass__)
... 或者更好的是:创建一个混入类,这个混入类会使用直接父类的 __metaclass__
:
class MyModel(Model):
__metaclass__ = MyMetaMixin, # Automagically uses `Model.__metaclass__`
原因是:为了在扩展现有应用时更灵活,我想创建一个全局机制,可以在 Django 中钩入 Model
、Form
等定义的过程,这样就可以在运行时进行更改。
一个通用的机制会比实现多个带回调的混入元类要好得多。
在你的帮助下,我终于想出了一个解决方案:元类 MetaProxy
。
这个想法是:创建一个元类,它调用一个回调来修改正在创建的类的命名空间,然后借助 __new__
,变成其中一个父类的元类。
#!/usr/bin/env python
#-*- coding: utf-8 -*-
# Magical metaclass
class MetaProxy(type):
""" Decorate the class being created & preserve __metaclass__ of the parent
It executes two callbacks: before & after creation of a class,
that allows you to decorate them.
Between two callbacks, it tries to locate any `__metaclass__`
in the parents (sorted in MRO).
If found — with the help of `__new__` method it
mutates to the found base metaclass.
If not found — it just instantiates the given class.
"""
@classmethod
def pre_new(cls, name, bases, attrs):
""" Decorate a class before creation """
return (name, bases, attrs)
@classmethod
def post_new(cls, newclass):
""" Decorate a class after creation """
return newclass
@classmethod
def _mrobases(cls, bases):
""" Expand tuple of base-classes ``bases`` in MRO """
mrobases = []
for base in bases:
if base is not None: # We don't like `None` :)
mrobases.extend(base.mro())
return mrobases
@classmethod
def _find_parent_metaclass(cls, mrobases):
""" Find any __metaclass__ callable in ``mrobases`` """
for base in mrobases:
if hasattr(base, '__metaclass__'):
metacls = base.__metaclass__
if metacls and not issubclass(metacls, cls): # don't call self again
return metacls#(name, bases, attrs)
# Not found: use `type`
return lambda name,bases,attrs: type.__new__(type, name, bases, attrs)
def __new__(cls, name, bases, attrs):
mrobases = cls._mrobases(bases)
name, bases, attrs = cls.pre_new(name, bases, attrs) # Decorate, pre-creation
newclass = cls._find_parent_metaclass(mrobases)(name, bases, attrs)
return cls.post_new(newclass) # Decorate, post-creation
# Testing
if __name__ == '__main__':
# Original classes. We won't touch them
class ModelMeta(type):
def __new__(cls, name, bases, attrs):
attrs['parentmeta'] = name
return super(ModelMeta, cls).__new__(cls, name, bases, attrs)
class Model(object):
__metaclass__ = ModelMeta
# Try to subclass me but don't forget about `ModelMeta`
# Decorator metaclass
class MyMeta(MetaProxy):
""" Decorate a class
Being a subclass of `MetaProxyDecorator`,
it will call base metaclasses after decorating
"""
@classmethod
def pre_new(cls, name, bases, attrs):
""" Set `washere` to classname """
attrs['washere'] = name
return super(MyMeta, cls).pre_new(name, bases, attrs)
@classmethod
def post_new(cls, newclass):
""" Append '!' to `.washere` """
newclass.washere += '!'
return super(MyMeta, cls).post_new(newclass)
# Here goes the inheritance...
class MyModel(Model):
__metaclass__ = MyMeta
a=1
class MyNewModel(MyModel):
__metaclass__ = MyMeta # Still have to declare it: __metaclass__ do not inherit
a=2
class MyNewNewModel(MyNewModel):
# Will use the original ModelMeta
a=3
class A(object):
__metaclass__ = MyMeta # No __metaclass__ in parents: just instantiate
a=4
class B(A):
pass # MyMeta is not called until specified explicitly
# Make sure we did everything right
assert MyModel.a == 1
assert MyNewModel.a == 2
assert MyNewNewModel.a == 3
assert A.a == 4
# Make sure callback() worked
assert hasattr(MyModel, 'washere')
assert hasattr(MyNewModel, 'washere')
assert hasattr(MyNewNewModel, 'washere') # inherited
assert hasattr(A, 'washere')
assert MyModel.washere == 'MyModel!'
assert MyNewModel.washere == 'MyNewModel!'
assert MyNewNewModel.washere == 'MyNewModel!' # inherited, so unchanged
assert A.washere == 'A!'
4 个回答
我不知道有什么方法可以“混合” metaclass(元类),但是你可以像处理普通类一样,继承和重写它们。
假设我有一个叫 BaseModel 的基础模型:
class BaseModel(object):
__metaclass__ = Blah
现在你想在一个叫 MyModel 的新类中继承这个基础模型,但你想在元类中添加一些额外的功能,同时保持原有的功能不变。要做到这一点,你可以这样做:
class MyModelMetaClass(BaseModel.__metaclass__):
def __init__(cls, *args, **kwargs):
do_custom_stuff()
super(MyModelMetaClass, cls).__init__(*args, **kwargs)
do_more_custom_stuff()
class MyModel(BaseModel):
__metaclass__ = MyModelMetaClass
一个类型只能有一个元类,因为元类的作用就是定义类的行为,多个元类在一起就没意义了。就像“链式”调用也没有意义:第一个元类创建了类型,那第二个元类又该干什么呢?
你需要把这两个元类合并在一起(就像处理其他类一样)。不过这可能会有点棘手,特别是当你不太了解它们的具体功能时。
class MyModelBase(type):
def __new__(cls, name, bases, attr):
attr['MyModelBase'] = 'was here'
return type.__new__(cls,name, bases, attr)
class MyMixin(type):
def __new__(cls, name, bases, attr):
attr['MyMixin'] = 'was here'
return type.__new__(cls, name, bases, attr)
class ChainedMeta(MyModelBase, MyMixin):
def __init__(cls, name, bases, attr):
# call both parents
MyModelBase.__init__(cls,name, bases, attr)
MyMixin.__init__(cls,name, bases, attr)
def __new__(cls, name, bases, attr):
# so, how is the new type supposed to look?
# maybe create the first
t1 = MyModelBase.__new__(cls, name, bases, attr)
# and pass it's data on to the next?
name = t1.__name__
bases = tuple(t1.mro())
attr = t1.__dict__.copy()
t2 = MyMixin.__new__(cls, name, bases, attr)
return t2
class Model(object):
__metaclass__ = MyModelBase # inherits from `ModelBase`
class MyModel(Model):
__metaclass__ = ChainedMeta
print MyModel.MyModelBase
print MyModel.MyMixin
如你所见,这里已经涉及到一些猜测,因为你并不真正知道其他元类的作用。如果这两个元类都很简单,这种合并可能有效,但我对这样的解决方案不太有信心。
为多个元类合并多个基类编写一个元类的任务留给读者自己去做 ;-P
我觉得你不能那样把它们连在一起,我也不知道那样会怎么运作。
不过,你可以在运行时创建新的 metaclass(元类)并使用它们。但这其实是一种很糟糕的黑科技。:)
zope.interface 做了一些类似的事情,它有一个顾问元类,会在类构造完成后对类做一些处理。如果之前已经有一个元类,它会在完成后把那个元类设置为新的元类。
(不过,除非你真的需要或者觉得这样做很有趣,否则最好避免这样做。)