2011-01-10 123 views
21

是否有可能鏈接元類?Metaclass Mixin或Chaining?

我有Model類,它使用__metaclass__=ModelBase來處理它的命名空間字典。我將繼承它,並「綁定」另一個元類,因此它不會遮擋原始的元類。

第一種方法是繼承class MyModelBase(ModelBase)

MyModel(Model): 
    __metaclass__ = MyModelBase # inherits from `ModelBase` 

但有可能只是鏈他們像混入,沒有明確的子類?喜歡的東西

class MyModel(Model): 
    __metaclass__ = (MyMixin, super(Model).__metaclass__) 

...甚至更好:創建使用__metaclass__從使用它的類的直接父一個mixin:

class MyModel(Model): 
    __metaclass__ = MyMetaMixin, # Automagically uses `Model.__metaclass__` 

的原因:爲了更靈活在擴展現有應用程序時,我想創建一個全局機制,以便在運行時更改Django中的ModelForm,...定義過程。

一個常見的機制會比使用回調mixins實現多個元類好得多。


有了您的幫助,我終於想出一個解決方案:元類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!' 
+0

在Python 3.4中,這看起來並不正確;第113行失敗(`MyModel`沒有`washere`屬性) – Joost 2016-08-06 09:54:13

回答

2

我的元類不要以爲你可以像那樣鏈接他們,我也不知道那會怎麼樣。

但是你可以在運行期間創建新的元類並使用它們。但這是一個可怕的黑客。 :)

zope.interface做了類似的事情,它有一個顧問元類,它將在構建完成後爲類做些事情。如果已經有一個metclass,那麼它會做的一件事情就是將之前的metaclass設置爲元類。

(但避免做這些事情,除非你有,或認爲這是有趣的。)

+0

`zope.interface`給了我一些想法,謝謝! :) – kolypto 2011-01-10 23:24:16

+0

Oooh,現在Django也會得到可怕的元類黑客。 Django *是*新的ZOpe。 ;-) – 2011-01-11 08:12:55

10

A型只能有一個元類,因爲元類簡單地陳述類的語句做什麼 - 有超過一個是沒有意義的。出於同樣的原因,「鏈接」沒有任何意義:第一個元類創建類型,那麼第二個應該做什麼?你將不得不合並兩個元類(就像其他類一樣)。但是這可能會很棘手,特別是如果你不知道他們做了什麼。

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

4

我不知道任何方式「混合」元類,但可以繼承和覆蓋它們就像你會正常的課程。

說我已經有了一個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