《what-is-a-metaclass-in-python》真的是非常棒(注意,本文中专指e-satis的回答),仔细阅读完这篇文章,基本上就搞清了metaclass
everything is object
python中,一切都是对象,比如一个数字、一个字符串、一个函数。对象是类(class)的是实例,类(class)也是对象,是type的实例。type对象本身又是type类的实例(鸡生蛋还是蛋生鸡?),因此我们称type为metaclass(中文元类)。在《python源码剖析》中,有清晰的表示
在python中,可以通过对象的__class__属性来查看对应的类,也可以通过isinstance来判断一个对象是不是某一个类的实例。例如:In [1]: class Obj(object):
...: a = 1
...:
In [2]: o = Obj()
...: print(o.__class__)
...:
In [3]: print(isinstance(o, Obj))
True
In [4]: print(Obj.__class__)
In [5]: isinstance(Obj,type)
Out[5]: True
In [6]: type.__class__
Out[6]: type
metaclass可以定制类的创建
我们都是通过class OBJ(obejct):pass的方式来创建一个类,上面有提到,类(class)是type类型的实例,按照我们常见的创建类的实例(instance)的方法,那么类(class)应该就是用 class="type"(*args)的方式创建的。确实如此,python document中有明确描述:class type(name, bases, dict)
With three arguments, return a new type object. This is essentially a dynamic form of the class statement.
The name string is the class name and becomes the __name__ attribute;
the bases tuple itemizes the base classes and becomes the __bases__ attribute;
and the dict dictionary is the namespace containing definitions for class body and becomes the __dict__ attribute.
For example, the following two statements create identical type objects:
该函数返回的就是一个class,三个参数分别是类名、基类列表、类的属性。比如在上面提到的OBJ类,完全等价于:Obj = type('OBJ', (), {'a': 1})
当然,用上面的方式创建一个类(class)看起来很傻,不过其好处在于可以动态的创建一个类。
python将定制类开放给了开发者,type也是一个类型,那么自然可以被继承,type的子类替代了Python默认的创建类(class)的行为,已探索的一些想法包括日志记录,接口检查,自动委托,自动属性创建,代理,框架和自动资源锁定/同步。
那么当我们用class Obj(obejct):pass的形式声明一个类的时候,怎么指定Obj的创建行为呢,那就是在类中使用__metaclass__。最简单的例子:class Metaclass(type):
def __new__(cls, name, bases, dct):
print 'HAHAHA'
dct['a'] = 1
return type.__new__(cls, name, bases, dct)
print 'before Create Obj'
class Obj(object):
__metaclass__ = Metaclass
print 'after Create Obj'
if __name__ == '__main__':
print Obj.a
运行结果:
before Create OBJ
HAHAHA
after Create OBJ
1
可以看到在代码执行的时候,在创建Obj这个类的时候,__metaclass__起了作用,为OBJ增加了一个类属性‘a'
关于__metaclass__的两个细节
首先,__metaclass__是一个callable即可,不一定非得是一个类,在what-is-a-metaclass-in-python就有__metaclass__是function的实例,也解释了为什么__metaclass__为一个类是更好的选择。
其次,就是如何查找并应用__metaclass__,python document中是有的:The appropriate metaclass is determined by the following precedence rules:
● If dict['__metaclass__'] exists, it is used.
● Otherwise, if there is at least one base class, its metaclass is used (this looks for a __class__ attribute first and if not found, uses its type).
● Otherwise, if a global variable named __metaclass__ exists, it is used.
● Otherwise, the old-style, classic metaclass (types.ClassType) is used.
意思是先从类的dict中查找,否则从基类的dict查找(这里会有一些需要注意的细节,后文会提到),否则从global作用域查找,否则使用默认的创建方式
python源码在ceval.c::build_class,核心代码如下static PyObject *
build_class(PyObject *methods, PyObject *bases, PyObject *name)
{
PyObject *metaclass = NULL, *result, *base;
if (PyDict_Check(methods))
metaclass = PyDict_GetItemString(methods, "__metaclass__");
if (metaclass != NULL)
Py_INCREF(metaclass);
else if (PyTuple_Check(bases) && PyTuple_GET_SIZE(bases) > 0) {
base = PyTuple_GET_ITEM(bases, 0);
metaclass = PyObject_GetAttrString(base, "__class__");
if (metaclass == NULL) {
PyErr_Clear();
metaclass = (PyObject *)base->ob_type;
Py_INCREF(metaclass);
}
}
else {
PyObject *g = PyEval_GetGlobals();
if (g != NULL && PyDict_Check(g))
metaclass = PyDict_GetItemString(g, "__metaclass__");
if (metaclass == NULL)
metaclass = (PyObject *) &PyClass_Type;
Py_INCREF(metaclass);
}
result = PyObject_CallFunctionObjArgs(metaclass, name, bases, methods,
NULL);
Py_DECREF(metaclass);
if (result == NULL && PyErr_ExceptionMatches(PyExc_TypeError)) {
/* A type error here likely means that the user passed
in a base that was not a class (such the random module
instead of the random.random type). Help them out with
by augmenting the error message with more information.*/
PyObject *ptype, *pvalue, *ptraceback;
PyErr_Fetch(&ptype, &pvalue, &ptraceback);
if (PyString_Check(pvalue)) {
PyObject *newmsg;
newmsg = PyString_FromFormat(
"Error when calling the metaclass bases"
" %s",
PyString_AS_STRING(pvalue));
if (newmsg != NULL) {
Py_DECREF(pvalue);
pvalue = newmsg;
}
}
PyErr_Restore(ptype, pvalue, ptraceback);
}
return result;
}
ceval::build_class
https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python