什么是元类(metaclass) ?也许对下面这些说法都不陌生:
- 元类是关于类的类,是类的模板。
- 元类是用来控制如何创建类的.正如类是创建对象的模板一样。
- 元类的实例为类,正如类的实例为对象。
这些说法都没有错,在概念之外进行一些更深入的探讨:元类是如何来控制类的创建的? 用户该如何定义自己的元类?在哪些情况下需要用到元类?使用元类可以解决什么 问题?
Python中一切皆对象,类也是对象,可以在运行的时候动态创建《当使用关键 宇class的时候, Python解释器在执行的时候就会创建一个对象(这里的对象是指类而非类的实例)。
def dynamic_class(name):
if name == 'A':
class A(object):
pass
return A
else:
class B(object):
pass
return B
UserClass = dynamic_class('A')
print(UserClass)
<class '__main__.dynamic_class.<locals>.A'>
UserClass()
<__main__.dynamic_class.<locals>.A at 0x7fb845622570>
既然类是对象,那么它就有其所届的类型,也一定还有什么东西能够控制它的生成。
通过 type
査看会发现 UserClass
的类型为 type
,而其对象 UserClass()
的类型为类A。
type(UserClass)
type
type(UserClass())
__main__.dynamic_class.<locals>.A
同时知道 type
还可以这样使用:
type(类名,父类的元组(针对继承的情况,可以为空)r包含属性的字典(名称和值))
例如:
A=type('A',(object,),{'value':2})
A.value
print(A)
<class '__main__.A'>
class C(A):
pass
print(C)
<class '__main__.C'>
print(C.__class__)
<class 'type'>
上例中 type
通过接受类的描述作为参数返回一个对象,这个对象可以被继承,属性能够被访问,
它实际是一个类,其创建由type控制,由 type
所创建的对象W_claSS_
属性为 type
。
type
实际上是Python的一个内建元类,用来直接指导类的生成。当然,除了使用内建元类 type
,
用户也可以通过继承 type
来自定义元类。
看一个利用元类实现强制类型检査的例子。
class TypeSetter(object):
def __init__(self,fieldtype):
print ("set attribute type", fieldtype)
self.fieldtype = fieldtype
def is_valid(self,value):
return isinstance (value,self.fieldtype)
class TypeCheckMeta(type):
def __new__(cls,name,bases, dict):
print ('----------------------')
print ("Allocating memory for class", name)
print (name)
print (bases)
print (dict)
return super(TypeCheckMeta, cls).__new__(cls,name,bases,dict)
def __init__(cls,name, bases, dict):
cls._fields = {}
for key,value in dict.items():
if isinstance(value,TypeSetter):
cls._fields[key]= value
def sayHi(cls):
print ("Hi")
Typesetter
用来设置属性的类型, TypeCheckMeta
为用户自定义的元类,
覆盖了 type
元类 中的__new__()
方法和__init__()
方法。
虽然也可以直接使用 TypeCheckMeta(name,bases,diet)
这种方式来创建类,
但更为常见的是在需要被生成的类中设置__metaclass__
属性,两种用法是等价的。
class TypeCheck(object):
__metaclass__ = TypeCheckMeta
def __setattr__(self,key,value):
print("set attribute value")
if key in self._fields:
if not self._fields[key].is_valid(value):
raise TypeError('Invalid type for field')
super(TypeCheck,self).__setattr__(key,value)
当类中设置了 __metaclass__
属性的时候,所有继承自该类的子类都将使用所设置的元 类来指导类的生成,因此上述程序的输出如下:
class MetaTest(TypeCheck):
name=TypeSetter(str)
num = TypeSetter(int)
set attribute type <class 'str'> set attribute type <class 'int'>
# mt = MetaTest()
# mt.name = "apple"
# mt.num = "test"
实际上.在新式类中当一个类未设置__metaclass__
属性的时候,
它将使用默认的 type
元类来生成类。而当该属性被设S时査找规则如下:
1)如果存在dict['__metaclass__']
.则使用对应的值来构建类;
否则使用其父类dict['__metaclass__']
中所指定的元类来构建类,
当父类中也不存在指定的metadass的情形下使用默认元类 type
。
2)对于古典类,条件1不满足的情况下,如果存在全局变量__metaclass__
,
则使用该 变量所对应的元类来构建类;齊则使 M typcs.ClassType
。
读者可以通过将上述例子中__metaclass__=TypeCheckMeta
设置为模块级别或者将 TypeCheck
改为古典类来验证上述査找规则。
需要额外提醒的是,元类中所定义的方法为其所创建的类的类方法,并不属于该类的对象。
因此上例中 mt.sayHi()
会抛出 AttributeError: 'MetaTest' object has no attribute 'sayHi'
错误,
而调用该方法的正确途径为 MetaTest.sayHi()
。
那么在什么悄况下会用到元类呢?有句话是这么说的:当你面临一个问题还在纠结要不 要使用元类的时候, 往往会有其他的更为简单的解决方案。
Python界的领袖Tim Peters曾这样说过:“元类就是深度的魔法,99%的用户应该根本 不必为此操心, 如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。 那些实际用到 元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。”
看几个使用元类的场景。
1)利用元类来实现单例模式。
class Singleton(type):
def __init__(cls, name, bases, dic):
super(Singleton,cls).__init__(name,bases,dic)
cls.instance = None
def __call__(cls,*args, **kwargs):
if cls.instance is None:
print ("creating & new instance")
cls.instance = super(Singleton,cls).__call__(*argsr **kwargs)
else:
print ("warning:only allowed to create one instance,roinstance already exists!")
return cls.instance
class NySingleton(object):
__metaclass__= Singleton
2)第二个例子来源于Python的标准库 string
, Template
, string
,
它提供简单的字符串替换功能。常见的使用例子如下:
Template('$name $age').substitute('name':'admin'),age=26)
该标准库的源代码中就用到了元类, Template
的元类为_Temp]ateMetadass
。
_TemplateMetadass
的 __init__()
方法通过査找属性( pattern
、 delimiter
和 idpatten
)
并将其构建为一个编译好的正则表达式存放在 pattern
属性中。
用户如果需要自定义分隔符(delimiter) 可以通过继承 Template
并薄盖它的类属性 delimiter
来实现。
string.Template
的部分源代码如下:
class Template:
"""A string class for supporting $-substitutions."""
__metaclass__ = _TemplateMetadass
delimiter = '$'
idpattern = r'[_a-z][_a-z0-9]*'
def __init__(self, template):
self.template = template
class _TemplateMetaClass(type):
pattern = r"""
%(delim)s(?:
(?P<escaped>%(delim)s) | # Escape sequence of two delimiters
(?P<named>%(id)s) | # delimiter and a Python identifier
(?P<braced>%(id)s) | # delimiter and a braced identifier
(?P<invalid>) | # Other ill-formed delimiter exprs
)"""
def __init__(cls, name, bases, dct):
super(_TemplateMetaclass, cls).__init__(name, bases, dct)
if 'pattern' in dct:
pattern = cls.pattern
else:
pattern = _TemplateMetaclass.pattern %(
'delim': _re.escape(cls.delimiter),
'id': cls.idpattern,
)
cls.pattern = _re.compile(pattern, _re.IGNORECASE | _re.VERBOSE)
另外在Django ORM、AOP编程中也有大量使用元类的惰形。 最后来谈谈关于元类需要注意的几点:
1)区别类方法与元方法(定义在元类中的方法>。看一个例子: Meta
和 SubMeta
都为元类,
其中 SubMeta
继承自 Meta
。因此f1、f2都为元方法,而 Test
为普通类, 其元类设置为 SubMeta
,
f3为类方法。
class Meta(type):
def f1(cls):
print("This is f1()")
class SubMeta(Meta):
def f2(cls):
print("This is f2()")
class Test(object):
__metaclass__ = SubMeta
@classmethod
def f3(cls):
print("I am f3()")
t = Test()
SubMeta.f1(Test)
This is f1()
Meta.f1(Test)
This is f1()
SubMeta.f2(Test)
This is f2()
# Test.f2()
# t.f2()
Test.f3()
I am f3()
t.f3()
I am f3()
上面的例子说明.元方法可以从元类或者类中调用,而不能从类的实例中调用; 但类方法可以从类中调用,也可以从类的实例中调用。
2)多继承需要严格限制,否则会产生冲突。
class M1(type):
def __new__(meta,name,bases,atts):
print("M1 called for" + name)
return super(M1,meta).__new__(meta,name,bases,atts)
class C1(object):
__metaclass__ =M1
class Sub1(C1):pass
class M2(type):
def __new__(meta,name,bases,atts):
print("M2 called for " + name)
return super(M2,meta).__new__(meta,name,bases,atts)
class C2(object):
__metaclass__ = M2
class Sub2(C1,C2):
pass
上面的例子中当Sub2同时继承自元类Cl和C2的时候会抛出异常,这是因为Python解释器并不知道C1和C2是否兼容,
因此会发出冲突督告=解决冲突的办法是重新定义一个派生自Ml和M2的元类,
并在C3中将其__metadass__
属性设置为该派生类。
class M3(M1,M2):
def __new__(meta,name,bases,atts):
print("M3 called for" + name)
return super(M3,meta).__new__(meta,name,bases,atts)
class C3(C1,C2):
__metaclass__ = M3
注意:元类用来指导类的生成,元方法可以从元类或者类中调用,不能从类的实例中调用, 而类方法既可以从类中调用也可以从类的实例中调用。