我粗略地了解了什么是元类。它们是类对象所基于的类(因为类是Python中的对象)。但是,有人可以解释(使用代码)如何创建一个。
答案 0 :(得分:6)
(在这一点上)元类中有两个关键方法:
__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__ = ...
来配置其元类,如下所示:
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
答案 1 :(得分:0)
我刚刚编写了一个完全注释的元类示例。它在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_elem\n"
" print \"Member init_elem: \" + self.init_elem\n"
)
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_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
首先调用元类“__call__
,然后调用”__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 })
您可以自己运行以查看其工作原理。
请注意,我故意将动态生成的类的名称与它们分配给的变量名不同。这是为了显示哪些名称实际有效。
另一个注意事项是我将“静态”放在引号中,我将其称为C ++中的概念,而不是Python装饰器。传统上我是一名C ++程序员,所以我仍然想以它的方式思考。