如何创建一个元类?

这个问题在这里已经有了答案:

  • Python中的元类是什么? 14个答案

  • 在这个元类中有两个关键的方法:

  • __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__方法不存在,并且类通过包括类变量speficies其元类__metaclass__ = ... ,如下所示:

    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
    

    我刚刚写了一个元类的完整评论的例子。 它在Python 2.7中。 我在这里分享它,希望它可以帮助您了解更多关于__new____init____call____dict__方法和有界/无界在Python的概念,以及使用元类的。

    我觉得元类的问题在于,它有太多的地方可以做同样的事情 ,或者类似但有一些细微差别 。 所以我的评论和测试案例主要强调在什么地方写什么 ,在某些地方的什么地方以及某个对象可以访问哪些地方

    该示例尝试构建类工厂,同时维护格式良好的类定义。

    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.
    

    输出看起来像这样(适合更好的演示):

    ================================================================
     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>}
    

    调用序列是元类' __new__然后是__init____call__不会被调用。

    如果我们创建一个实例,

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

    输出是:

    ================================================================
     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_elemn   print "Member init_elem: " + self.init_elemn',
     '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
    

    元类' __call__被调用,然后是类__new____init__

    比较每个对象的打印成员,您可以发现它们何时何地被添加或更改,就像我在代码中所评论的一样。

    我也运行以下测试用例:

    factory.clsVar()    # Will raise exception
    Factory.clsVar()
    factory.metaVar()
    factory.selfVar()
    
    func2 = (
        "@classmethodn"
        "def printClassID(cls):n"
        "   print "Class ID: %02d" % cls.class_idn"
        )
    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”放在引号中,我将其引用为C ++中的概念,而不是Python装饰器。 传统上我是一名C ++程序员,所以我仍然喜欢思考。

    链接地址: http://www.djcxy.com/p/6985.html

    上一篇: How does one create a metaclass?

    下一篇: How are Python metaclasses different from regular class inheritance?