35

メタクラスを連鎖させることは可能ですか?

名前空間dictを処理するため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の定義のプロセスModelにフックするためのグローバルメカニズムを作成したいと思います。Form

一般的なメカニズムは、コールバックミックスインを使用して複数のメタクラスを実装するよりもはるかに優れています。


あなたの助けを借りて、私はついに解決策を思いつくことができました:メタクラスMetaProxy

アイデアは次のとおりです。コールバックを呼び出して作成中のクラスの名前空間を変更するメタクラスを作成し、次に、の助けを借りて__new__、親の1つのメタクラスに変更します。

#!/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

4 に答える 4

16

メタクラスは単にクラスステートメントの機能を示すため、型に含めることができるメタクラスは1つだけです。複数あることは意味がありません。同じ理由で、「連鎖」は意味がありません。最初のメタクラスが型を作成するので、2番目のメタクラスは何をすることになっていますか?

2つのメタクラスをマージする必要があります(他のクラスと同じように)。しかし、特に彼らが何をしているのか本当にわからない場合は、それは難しいかもしれません。

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

于 2011-01-10T22:38:33.780 に答える
8

メタクラスを「混合」する方法はわかりませんが、通常のクラスと同じように、メタクラスを継承してオーバーライドできます。

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
于 2013-02-05T22:54:24.723 に答える
7

そのように連鎖させることはできないと思いますし、それがどのように機能するかもわかりません。

ただし、実行時に新しいメタクラスを作成して使用することはできます。しかし、それは恐ろしいハックです。:)

zope.interfaceは似たようなことをします、それはアドバイザーメタクラスを持っています、それは構築後にクラスにいくつかのことをするだけです。すでにmetclassがあった場合、それが行うことの1つは、終了すると、その前のメタクラスをメタクラスとして設定します。

(ただし、必要がない限り、または楽しいと思わない限り、このようなことは避けてください。)

于 2011-01-10T22:16:52.667 に答える
0

@jochenritzelによる回答に加えて、以下は結合ステップを単純化します。

def combine_classes(*args):
    name = "".join(a.__name__ for a in args)
    return type(name, args, {})


class ABCSomething(object, metaclass=combine_classes(SomethingMeta, ABCMeta)):
    pass

ここではtype(name, bases, dict)、動的classステートメントのように機能します(ドキュメントを参照)。驚いたことに、 2番目のステップでdictを設定するための引数を使用する方法はないようです。metaclassそうでなければ、プロセス全体を1つの関数呼び出しに単純化することができます。

于 2020-07-11T13:29:34.527 に答える