2011-01-10 2 views
21

메타 쇠를 연결할 수 있습니까?Metaclass Mixin 또는 Chaining?

나는 그 네임 스페이스 딕셔너리를 처리하는 __metaclass__=ModelBase를 사용하는 클래스 Model 있습니다. 나는 그걸 상속하여 다른 메타 클래스를 "묶어"원래의 그늘을 그늘지게하지 않을 것입니다.

첫 번째 방법은 class MyModelBase(ModelBase)를 서브 클래 싱하는 것입니다

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

를하지만 명시 적으로 서브 클래 싱하지 않고, 단지 유지 mixin 같은 체인들을 수 있습니까? 더 나은

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

... 나 같은 뭔가 :

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

이유 : 더 많은 유연성을 위해 그것을 사용하는 클래스의 바로 부모로부터 __metaclass__을 사용하는 믹스 인을 만들 기존 앱을 확장에서, 나는 Model, Form의 과정에 접선을위한 글로벌 메커니즘을 만들려면 ... 장고의 정의는 그래서 런타임에 변경 될 수 있습니다.

일반적인 메커니즘은 콜백이나 mixin 여러 메타 클래스를 구현하는 것보다 훨씬 더 좋을 것이다. 메타 클래스 MetaProxy : 당신의 도움으로


나는 마침내 해결책을 마련 할 수 있었다.

아이디어입니다 : 부모 중 하나

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

파이썬 3.4에서는 올바르게 어설 션 된 것처럼 보이지 않습니다. 113 행에 실패 ('MyModel'에는'washere' 속성이 없습니다.) – Joost

답변

2

I의 메타 클래스에 돌연변이, 클래스의 네임 스페이스가 __new__의 도움으로, 다음, 생성되고 수정 콜백을 호출하는 메타 클래스를 만들 너는 그것들을 사슬처럼 묶을 수 있다고 생각하지 않는다. 나는 그것이 어떻게 작용할 지 모른다.

하지만 당신은 런타임 동안 새로운 메타 클래스를 만들어 사용할 수 있습니다. 그러나 그것은 무시 무시한 해킹이다. :)

zope.interface 비슷한 무언가를, 그것은 단지 건설 후 클래스에 어떤 일을 할 것 고문의 메타 클래스가 있습니다. 이미 메타 클래스가있는 경우 수행 할 작업 중 하나가 완료되면 이전 메타 클래스를 메타 클래스로 설정합니다.

(당신이, 또는 재미 있다고 생각하지 않는 그러나 사물의 이러한 종류의 일을 피할 수 있습니다.) 메타 클래스는 단순히 클래스 문이 무엇을 주장하기 때문에

+0

'zope.interface'가 아이디어를주었습니다. 감사합니다! :) – kolypto

+0

Oooh, 이제 Django는 무서운 메타 크래킹 해킹을 받게됩니다. Django *는 새로운 ZOpe입니다. ;-) –

10

이 유형은, 단 하나의 메타 클래스를 가질 수 있습니다 -보다 더 필요 하나는 말이되지 않습니다. 같은 이유로 "연결"은 의미가 없습니다. 첫 번째 메타 클래스가 유형을 생성하므로 두 번째로해야 할 일이 무엇입니까?

당신은 (다른 모든 클래스와 같은)이 메타 클래스를 병합해야합니다

. 그러나 그것은 특히 자신이하는 일을 정말로 모르는 경우에 까다로울 수 있습니다.

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

+0

'MyModelBase'에 대한 명시적인 의존성을 제외하고는 모든 것이 훌륭합니다 :) – kolypto

+8

"동일한 이유로"연결이 의미가 없습니다. - 왜 그런가요? 연결하는 메타 클래스는 상속을 통해 수행되어야합니다. 실제로, 여러분의 메타 클래스가 내장 메타 클래스'type'을 확장하기 때문에 여러분은 이미 그것들을 연결하고 있습니다. super()를 통해 메타 클래스를 호출하면 여러 상속을 통해 쉽게 연결된 메타 클래스를 얻을 수 있습니다. –

+0

"하나의 메타 클래스는 하나의 메타 클래스 만 가질 수 있습니다. 왜냐하면 메타 클래스는 클래스 문이 수행하는 것을 간단하게 기술하기 때문입니다."- 더 적절하게 말하면 : 메타 클래스는 유형의 유형입니다. 그래서 위의 코드에서 MyModel의 유형은 ChainedMeta (try 유형 (MyModel))입니다. 즉, MyModel은 ChainedMeta의 인스턴스입니다. 분명히 모든 클래스는 모든 객체와 마찬가지로 하나의 유형 만 가질 수 있으므로 메타 클래스 만 가질 수 있습니다. 명확성을 위해서 – nadapez

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