2016-03-01 14 views
6

私は、どのようなメタクラスが大まかに考えています。それらはクラスオブジェクトが基づいているクラスです(クラスはPythonのオブジェクトなので)。しかし、誰かが(コードを使って)作成方法について説明することができます。どのようにしてメタクラスを作成しますか?

+0

メタクラスの*理由*については、[この回答](http://stackoverflow.com/a/6581949/208880)を参照してください。 –

+0

@PeterMortensen:いいえ、その質問はオフサイトのリソースを探しています。 –

答えて

4

がある二つの重要なメタクラスのメソッド(この時点で):

  • __prepare__、および
  • __new__

__prepare__あなたは(そのようOrderedDictをなど)カスタムマッピングを供給することができますクラスが作成されている間に名前空間として使用されます。選択した名前空間のインスタンスを返す必要があります。 __prepare__を実装していない場合は、通常のdictが使用されます。

__new__は最終的なクラスの実際の作成/変更を担当します。

最低限、何もしない、余分なメタクラスは、次のようになります。

class Meta(type): 

    def __prepare__(metaclass, cls, bases): 
     return dict() 

    def __new__(metacls, cls, bases, clsdict): 
     return super().__new__(metacls, cls, bases, clsdict) 

簡単な例:

は、あなたの属性に実行するためのいくつかの簡単な検証コードをしたいと言う - それが好きintまたはstrである必要があります。あなたが見ることができるように、あなたが二回属性の名前を繰り返す必要があり

class Person: 
    weight = ValidateType('weight', int) 
    age = ValidateType('age', int) 
    name = ValidateType('name', str) 

:メタクラスなければ、あなたのクラスには、次のようになります。これは、刺激的なバグと共にタイプミスを可能にします。

簡単なメタクラスは、その問題に対処することができます

class Person(metaclass=Validator): 
    weight = ValidateType(int) 
    age = ValidateType(int) 
    name = ValidateType(str) 

これはメタクラスは、(それが必要とされていないので、__prepare__を使用していない)のようになります。:

class Validator(type): 
    def __new__(metacls, cls, bases, clsdict): 
     # search clsdict looking for ValidateType descriptors 
     for name, attr in clsdict.items(): 
      if isinstance(attr, ValidateType): 
       attr.name = name 
       attr.attr = '_' + name 
     # create final class and return it 
     return super().__new__(metacls, cls, bases, clsdict) 

のサンプルを実行します。

p = Person() 
p.weight = 9 
print(p.weight) 
p.weight = '9' 

は生成します。

9 
Traceback (most recent call last): 
    File "simple_meta.py", line 36, in <module> 
    p.weight = '9' 
    File "simple_meta.py", line 24, in __set__ 
    (self.name, self.type, value)) 
TypeError: weight must be of type(s) <class 'int'> (got '9') 

この例では、十分に単純であるノート

それはまた、クラスのデコレータで実現されている可能性が、おそらく実際のメタクラスは、はるかにやっているでしょう。

Python 2。X、__prepare__方法は存在せず、クラスは次のように、可変__metaclass__ = ...クラスを含むことによって、そのメタクラスをspeficies:

class Person(object): 
    __metaclass__ = ValidateType 

参照のための 'ValidateType' クラス:

class ValidateType: 
    def __init__(self, type): 
     self.name = None # will be set by metaclass 
     self.attr = None # will be set by metaclass 
     self.type = type 
    def __get__(self, inst, cls): 
     if inst is None: 
      return self 
     else: 
      return inst.__dict__[self.attr] 
    def __set__(self, inst, value): 
     if not isinstance(value, self.type): 
      raise TypeError('%s must be of type(s) %s (got %r)' % 
        (self.name, self.type, value)) 
     else: 
      inst.__dict__[self.attr] = value 
0

I」はちょうど完全にコメントされたメタクラスの例を書いただけです。これはPython 2.7です。私はここでそれを共有していて、__new____init____call____dict__の方法と、Pythonでの有界/無制限の概念、およびメタクラスの使用についての詳細を理解するのに役立つことを願っています。

メタクラスの問題は、私が感じて、それはあなたには、いくつかのわずかな違いとまだ同じこと、または同様の操作を行うことができあまりにも多くの場所を持っていることです。だから、私のコメントとテストケースは、主にを書いています。どこに行くのですか。は特定のオブジェクトにアクセスできます。

この例では、整形式のクラス定義を維持しながらクラスファクトリを作成しようとしています。

================================================================ 
Metaclass's __new__ (creates class objects) 
---------------------------------------------------------------- 
Bounded to object: <class '__main__.FactoryMeta'> 
Bounded object's __dict__: 
{ ..., 
'clsVar': <function clsVar at 0x00000000029BC828>, 
'count': 1, 
'extra': 'default extra', 
'metaVar': <classmethod object at 0x00000000029B4B28>, 
'var': "Change made to <class '__main__.FactoryMeta'> by metaclass' __new__"} 
---------------------------------------------------------------- 
Parameter 'name': Factory 
Parameter 'bases': (<type 'object'>,) 
Parameter 'dict': 
{ ..., 
'classFactory': <classmethod object at 0x00000000029B4DC8>, 
'extra': 'overridng extra', 
'new_elem': 'effective', 
'selfVar': <function selfVar at 0x00000000029BC6D8>} 

================================================================ 
Metaclass's __init__ (initiates class objects) 
---------------------------------------------------------------- 
Bounded to object: <class '__main__.Factory'> 
Bounded object's __dict__: 
{ ..., 
'classFactory': <classmethod object at 0x00000000029B4DC8>, 
'extra': 'overridng extra', 
'new_elem': 'effective', 
'selfVar': <function selfVar at 0x00000000029BC6D8>, 
'var': "Change made to <class '__main__.Factory'> by metaclass' __init__"} 
---------------------------------------------------------------- 
Parameter 'name': Factory 
Parameter 'bases': (<type 'object'>,) 
Parameter 'dict': 
{ ..., 
'classFactory': <classmethod object at 0x00000000029B4DC8>, 
'extra': 'overridng extra', 
'init_elem': 'defective', 
'new_elem': 'effective', 
'selfVar': <function selfVar at 0x00000000029BC6D8>} 

呼び出しシーケンスは、その__init__メタクラス__new__です:

from pprint import pprint 
from types import DictType 

class FactoryMeta(type): 
    """ Factory Metaclass """ 

    # @ Anything "static" (bounded to the classes rather than the instances) 
    # goes in here. Or use "@classmethod" decorator to bound it to meta. 
    # @ Note that these members won't be visible to instances, you have to 
    # manually add them to the instances in metaclass' __call__ if you wish 
    # to access them through a instance directly (see below). 
    extra = "default extra" 
    count = 0 

    def clsVar(cls): 
     print "Class member 'var': " + str(cls.var) 

    @classmethod 
    def metaVar(meta): 
     print "Metaclass member 'var': " + str(meta.var) 

    def __new__(meta, name, bases, dict): 
     # @ Metaclass' __new__ serves as a bi-functional slot capable for 
     # initiating the classes as well as alternating the meta. 
     # @ Suggestion is putting majority of the class initialization code 
     # in __init__, as you can directly reference to cls there; saving 
     # here for anything you want to dynamically added to the meta (such 
     # as shared variables or lazily GC'd temps). 
     # @ Any changes here to dict will be visible to the new class and their 
     # future instances, but won't affect the metaclass. While changes 
     # directly through meta will be visible to all (unless you override 
     # it later). 
     dict['new_elem'] = "effective" 
     meta.var = "Change made to %s by metaclass' __new__" % str(meta) 
     meta.count += 1 
     print "================================================================" 
     print " Metaclass's __new__ (creates class objects)" 
     print "----------------------------------------------------------------" 
     print "Bounded to object: " + str(meta) 
     print "Bounded object's __dict__: " 
     pprint(DictType(meta.__dict__), depth = 1) 
     print "----------------------------------------------------------------" 
     print "Parameter 'name': " + str(name) 
     print "Parameter 'bases': " + str(bases) 
     print "Parameter 'dict': " 
     pprint(dict, depth = 1) 
     print "\n" 
     return super(FactoryMeta, meta).__new__(meta, name, bases, dict) 

    def __init__(cls, name, bases, dict): 
     # @ Metaclass' __init__ is the standard slot for class initialization. 
     # Classes' common variables should mainly goes in here. 
     # @ Any changes here to dict won't actually affect anything. While 
     # changes directly through cls will be visible to the created class 
     # and its future instances. Metaclass remains untouched. 
     dict['init_elem'] = "defective" 
     cls.var = "Change made to %s by metaclass' __init__" % str(cls) 
     print "================================================================" 
     print " Metaclass's __init__ (initiates class objects)" 
     print "----------------------------------------------------------------" 
     print "Bounded to object: " + str(cls) 
     print "Bounded object's __dict__: " 
     pprint(DictType(cls.__dict__), depth = 1) 
     print "----------------------------------------------------------------" 
     print "Parameter 'name': " + str(name) 
     print "Parameter 'bases': " + str(bases) 
     print "Parameter 'dict': " 
     pprint(dict, depth = 1) 
     print "\n" 
     return super(FactoryMeta, cls).__init__(name, bases, dict) 

    def __call__(cls, *args): 
     # @ Metaclass' __call__ gets called when a class name is used as a 
     # callable function to create an instance. It is called before the 
     # class' __new__. 
     # @ Instance's initialization code can be put in here, although it 
     # is bounded to "cls" rather than instance's "self". This provides 
     # a slot similar to the class' __new__, where cls' members can be 
     # altered and get copied to the instances. 
     # @ Any changes here through cls will be visible to the class and its 
     # instances. Metaclass remains unchanged. 
     cls.var = "Change made to %s by metaclass' __call__" % str(cls) 
     # @ "Static" methods defined in the meta which cannot be seen through 
     # instances by default can be manually assigned with an access point 
     # here. This is a way to create shared methods between different 
     # instances of the same metaclass. 
     cls.metaVar = FactoryMeta.metaVar 
     print "================================================================" 
     print " Metaclass's __call__ (initiates instance objects)" 
     print "----------------------------------------------------------------" 
     print "Bounded to object: " + str(cls) 
     print "Bounded object's __dict__: " 
     pprint(DictType(cls.__dict__), depth = 1) 
     print "\n" 
     return super(FactoryMeta, cls).__call__(*args) 

class Factory(object): 
    """ Factory Class """ 

    # @ Anything declared here goes into the "dict" argument in the metaclass' 
    # __new__ and __init__ methods. This provides a chance to pre-set the 
    # member variables desired by the two methods, before they get run. 
    # @ This also overrides the default values declared in the meta. 
    __metaclass__ = FactoryMeta 
    extra = "overridng extra" 

    def selfVar(self): 
     print "Instance member 'var': " + str(self.var) 

    @classmethod 
    def classFactory(cls, name, bases, dict): 
     # @ With a factory method embedded, the Factory class can act like a 
     # "class incubator" for generating other new classes. 
     # @ The dict parameter here will later be passed to the metaclass' 
     # __new__ and __init__, so it is the right place for setting up 
     # member variables desired by these two methods. 
     dict['class_id'] = cls.__metaclass__.count # An ID starts from 0. 
     # @ Note that this dict is for the *factory product classes*. Using 
     # metaclass as callable is another way of writing class definition, 
     # with the flexibility of employing dynamically generated members 
     # in this dict. 
     # @ Class' member methods can be added dynamically by using the exec 
     # keyword on dict. 
     exec(cls.extra, dict) 
     exec(dict['another_func'], dict) 
     return cls.__metaclass__(name + ("_%02d" % dict['class_id']), bases, dict) 

    def __new__(cls, function): 
     # @ Class' __new__ "creates" the instances. 
     # @ This won't affect the metaclass. But it does alter the class' member 
     # as it is bounded to cls. 
     cls.extra = function 
     print "================================================================" 
     print " Class' __new__ (\"creates\" instance objects)" 
     print "----------------------------------------------------------------" 
     print "Bounded to object: " + str(cls) 
     print "Bounded object's __dict__: " 
     pprint(DictType(cls.__dict__), depth = 1) 
     print "----------------------------------------------------------------" 
     print "Parameter 'function': \n" + str(function) 
     print "\n" 
     return super(Factory, cls).__new__(cls) 

    def __init__(self, function, *args, **kwargs): 
     # @ Class' __init__ initializes the instances. 
     # @ Changes through self here (normally) won't affect the class or the 
     # metaclass; they are only visible locally to the instances. 
     # @ However, here you have another chance to make "static" things 
     # visible to the instances, "locally". 
     self.classFactory = self.__class__.classFactory 
     print "================================================================" 
     print " Class' __init__ (initiates instance objects)" 
     print "----------------------------------------------------------------" 
     print "Bounded to object: " + str(self) 
     print "Bounded object's __dict__: " 
     pprint(DictType(self.__dict__), depth = 1) 
     print "----------------------------------------------------------------" 
     print "Parameter 'function': \n" + str(function) 
     print "\n" 
     return super(Factory, self).__init__(*args, **kwargs) 
# @ The metaclass' __new__ and __init__ will be run at this point, where the 
# (manual) class definition hitting its end. 
# @ Note that if you have already defined everything well in a metaclass, the 
# class definition can go dummy with simply a class name and a "pass". 
# @ Moreover, if you use class factories extensively, your only use of a 
# manually defined class would be to define the incubator class. 

出力は、この(より良いデモンストレーションのために適合)のように見えます。 __call__は現時点では呼び出されません。

そして、我々は、インスタンスを作成した場合、

func1 = (
    "def printElems(self):\n" 
    " print \"Member new_elem: \" + self.new_elem\n" 
    " print \"Member init_elem: \" + self.init_elem\n" 
    ) 
factory = Factory(func1) 

出力は次のとおりです。最初に呼び出される__call__、その後、クラスの

================================================================ 
Metaclass's __call__ (initiates instance objects) 
---------------------------------------------------------------- 
Bounded to object: <class '__main__.Factory'> 
Bounded object's __dict__: 
{ ..., 
'classFactory': <classmethod object at 0x00000000029B4DC8>, 
'extra': 'overridng extra', 
'metaVar': <bound method type.metaVar of <class '__main__.FactoryMeta'>>, 
'new_elem': 'effective', 
'selfVar': <function selfVar at 0x00000000029BC6D8>, 
'var': "Change made to <class '__main__.Factory'> by metaclass' __call__"} 

================================================================ 
Class' __new__ ("creates" instance objects) 
---------------------------------------------------------------- 
Bounded to object: <class '__main__.Factory'> 
Bounded object's __dict__: 
{ ..., 
'classFactory': <classmethod object at 0x00000000029B4DC8>, 
'extra': 'def printElems(self):\n print "Member new_elem: " + self.new_elem\n print "Member init_elem: " + self.init_elem\n', 
'metaVar': <bound method type.metaVar of <class '__main__.FactoryMeta'>>, 
'new_elem': 'effective', 
'selfVar': <function selfVar at 0x00000000029BC6D8>, 
'var': "Change made to <class '__main__.Factory'> by metaclass' __call__"} 
---------------------------------------------------------------- 
Parameter 'function': 
def printElems(self): 
    print "Member new_elem: " + self.new_elem 
    print "Member init_elem: " + self.init_elem 

================================================================ 
Class' __init__ (initiates instance objects) 
---------------------------------------------------------------- 
Bounded to object: <__main__.Factory object at 0x00000000029BB7B8> 
Bounded object's __dict__: 
{'classFactory': <bound method FactoryMeta.classFactory of <class '__main__.Factory'>>} 
---------------------------------------------------------------- 
Parameter 'function': 
def printElems(self): 
    print "Member new_elem: " + self.new_elem 
    print "Member init_elem: " + self.init_elem 

メタクラス__new____init__

各オブジェクトの印刷されたメンバーを比較すると、コードでコメントしたときと同じように、追加または変更された場所と場所を知ることができます。

私はまた、次のテストケースを実行します。

factory.clsVar() # Will raise exception 
Factory.clsVar() 
factory.metaVar() 
factory.selfVar() 

func2 = (
    "@classmethod\n" 
    "def printClassID(cls):\n" 
    " print \"Class ID: %02d\" % cls.class_id\n" 
    ) 
ProductClass1 = factory.classFactory("ProductClass", (object,), { 'another_func': func2 }) 

product = ProductClass1() 
product.printClassID() 
product.printElems() # Will raise exception 

ProductClass2 = Factory.classFactory("ProductClass", (Factory,), { 'another_func': "pass" }) 
ProductClass2.printClassID() # Will raise exception 
ProductClass3 = ProductClass2.classFactory("ProductClass", (object,), { 'another_func': func2 }) 

あなたはそれがどのように動作するか確認するために自分で実行できます。

私は意図的に、動的に生成されたクラスの名前を、割り当てられた変数名とは異なるものにしました。実際にどの名前が表示されているかを表示します。

もうひとつ注意したいのは、引用符で囲まれた "static"です。私はPythonデコレータではなくC++のような概念を参照しています。伝統的に私はC++のプログラマーなので、私はまだその考え方を考えています。

関連する問題