How does one create a metaclass?
我对什么是元类有个大致的概念。它们是类对象所基于的类(因为类是Python中的对象)。但有人能解释(用代码)一个人如何去创造一个。
在元类中(此时)有两个关键方法:
__prepare__ 和__new__
一个赤裸裸的骨骼,什么都不做,额外的元类看起来像:
1 2 3 4 5 6 7 | class Meta(type): def __prepare__(metaclass, cls, bases): return dict() def __new__(metacls, cls, bases, clsdict): return super().__new__(metacls, cls, bases, clsdict) |
一个简单的例子:
假设您希望在您的属性上运行一些简单的验证代码——比如它必须始终是一个
1 2 3 4 | class Person: weight = ValidateType('weight', int) age = ValidateType('age', int) name = ValidateType('name', str) |
如您所见,您必须重复属性的名称两次。这使得打字错误和令人恼火的错误成为可能。
一个简单的元类可以解决这个问题:
1 2 3 4 | class Person(metaclass=Validator): weight = ValidateType(int) age = ValidateType(int) name = ValidateType(str) |
这就是元类的样子(不使用
1 2 3 4 5 6 7 8 9 | 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) |
运行示例:
1 2 3 4 | p = Person() p.weight = 9 print(p.weight) p.weight = '9' |
生产:
1 2 3 4 5 6 7 | 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中,
1 2 | class Person(object): __metaclass__ = ValidateType |
要引用的"validateType"类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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 |
我刚刚写了一个元类的完全注释示例。在Python2.7中。我在这里分享它,希望它能帮助您了解更多关于
我觉得元类的问题在于,它有太多地方可以做同样的事情,或者类似的事情,但是有一些细微的差别。因此,我的注释和测试用例主要强调在哪里写什么,在特定的点到哪里去,以及特定对象可以访问什么。
该示例尝试在维护格式良好的类定义的同时构建类工厂。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | 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"\ " 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"\ " 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"\ " 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': \ " + str(function) print"\ " 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': \ " + str(function) print"\ " 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. |
输出如下(为了更好的演示而调整):
[cc lang="python"]元类的新对象(创建类对象)——————————————————————————————————————————————————————————————————————————————————————————————————————————————--绑定到对象: