先前学习看到ORM的时候,需要用到metaclass相关的知识,于是,回过头来又去看关于metaclass的知识,看metaclass的时候,我又发现,一些和super相关的知识掌握的不是很透彻,于是又去复习了一下super相关的知识,看super的时候,又发现不了解Python的MRO,于是又去看MRO,MRO,super看完之后,又发现还需要看构造个性化的类相关的知识(__init__,与__new__),相关的都看完之后,终于又回到metaclass上来了。还好,看到了一篇翻译自stack overflow的文章,讲得特别好,无论是他书写的内容,还是他书写的方式,都非常的值得学习。
元类就是创建其他类的类。
作者先从类也是对象这个命题入手(相比于其他面向对象语言,这个概念扭转不过来,影响对一些Python语法的理解),它是一组描述如何生成一个对象的代码,它具有实例化一个对象的能力,不过,它本质上是还是一个对象,Python解释器执行完class这样的语句之后就在内存里面创建一个类的对象,它也可以执行像其他的普通对象一样的操作,比如,将它赋值给另一个变量(类似于取个别名),拷贝它,增加它的属性,把它作为参数进行传递。
介绍了类之后,紧接着引入另一个话题“如何动态的创建类”。class语法定义的类,属于静态定义的类,如何动态的,创建,如何使某些类,具有相似的特性,这都是metaclass可以做到的。作者举了两个不同级别“动态”类的例子,先是一个函数内定义多个类,利用传入的函数的字符串来决定返回类的例子,又举了用type创建类的例子,type(类名,父类的元组(可以为空),包含属性的字典)。
介绍了以上的基础知识后,作者终于切入到主题了,元类到底是什么:
元类就是创建类的类,type就是Python内建的元类,当然你也可以创建自己的元类。
先介绍__metaclass__属性
这个属性会影响类的创建,解释器会现在类体内,然后类的祖先类,模块去查找这个属性,找到就用它去创建类,找不到,就用type去创建。__metaclass__属性,为callable对象,函数或者类,但是他们必须返回一个类,
很显然函数的话,肯定要使用type()返回类,使用类的话肯定是元类,即type或者它的派生类。
一个把类属性名称全部统一成“大写”的例子
用函数实现__metaclass__属性:
def upper_attr(future_class_name, future_class_parents, future_class_attr): ‘‘‘返回一个类对象,将属性都转为大写形式‘‘‘ # 选择所有不以‘__‘开头的属性 attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith(‘__‘)) # 将它们转为大写形式 uppercase_attr = dict((name.upper(), value) for name, value in attrs) # 通过‘type‘来做类对象的创建 return type(future_class_name, future_class_parents, uppercase_attr) __metaclass__ = upper_attr # 这会作用到这个模块中的所有类 class Foo(object): # 我们也可以只在这里定义__metaclass__,这样就只会作用于这个类中 bar = ‘bip‘ print hasattr(Foo, ‘bar‘) # 输出: False print hasattr(Foo, ‘BAR‘) # 输出:True f = Foo() print f.BAR # 输出:‘bip‘
一个自定义元类来实现:
演化之后所谓符合面向对象的版本:
class UpperAttrMetaclass(type): def __new__(cls, name, bases, dct): attrs = ((name, value) for name, value in dct.items() if not name.startswith(‘__‘)) uppercase_attr = dict((name.upper(), value) for name, value in attrs) return super(UpperAttrMetaclass, cls).__new__(cls, name, bases, uppercase_attr)
我有个疑问就是:修改后的字典重新作为参数,被传递到type的__new__()方法,在这个方法里面就把这个字典赋值到新创建的类对象里面了?这个修改在__new__()就能得到体现了?这个地方,暂时不明白__new__()与__init__如何合作的????
不用再调用type的__init__()?
一个小结论就是:定义calss的语句,定义的类只是一个半成品,Python允许程序员通过__metaclass__属性再来定义自己的构造类对象的规则,并且,解释器会把已经得到的“半成品”的三项重要信息传递给__metaclass__属性的callable对象,供他使用,使它生成新的type实例。
“。使用到元类的代码比较复杂,这背后的原因倒并不是因为元类本身,而是因为你通常会使用元类去做一些晦涩的事情,依赖于自省,控制继承等等。确实,用元类来搞些“黑暗魔法”是特别有用的,因而会搞出些复杂的东西来。但就元类本身而言,它们其实是很简单的:”
1) 拦截类的创建
2) 修改类
3) 返回修改之后的类
为什么要用metaclass类而不是函数?
由于__metaclass__可以接受任何可调用的对象,那为何还要使用类呢,因为很显然使用类会更加复杂啊?这里有好几个原因:
1) 意图会更加清晰。当你读到UpperAttrMetaclass(type)时,你知道接下来要发生什么。
2) 你可以使用OOP编程。元类可以从元类中继承而来,改写父类的方法。元类甚至还可以使用元类。
3) 你可以把代码组织的更好。当你使用元类的时候肯定不会是像我上面举的这种简单场景,通常都是针对比较复杂的问题。将多个方法归总到一个类中会很有帮助,也会使得代码更容易阅读。
4) 你可以使用__new__, __init__以及__call__这样的特殊方法。它们能帮你处理不同的任务。就算通常你可以把所有的东西都在__new__里处理掉,有些人还是觉得用__init__更舒服些。
5) 哇哦,这东西的名字是metaclass,肯定非善类,我要小心!
究竟为什么要使用元类?
现在回到我们的大主题上来,究竟是为什么你会去使用这样一种容易出错且晦涩的特性?好吧,一般来说,你根本就用不上它:
“元类就是深度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。” —— Python界的领袖 Tim Peters
元类的主要用途是创建API。一个典型的例子是Django ORM。它允许你像这样定义:
1
2
3
class
Person(models.Model):
name
=
models.CharField(max_length
=
30
)
age
=
models.IntegerField()
但是如果你像这样做的话:
1
2
guy
=
Person(name
=
‘bob‘
, age
=
‘35‘
)
print
guy.age
这并不会返回一个IntegerField对象,而是会返回一个int,甚至可以直接从数据库中取出数据。这是有可能的,因为models.Model定义了__metaclass__, 并且使用了一些魔法能够将你刚刚定义的简单的Person类转变成对数据库的一个复杂hook。Django框架将这些看起来很复杂的东西通过暴露出一个简单的使用元类的API将其化简,通过这个API重新创建代码,在背后完成真正的工作。
结语
首先,你知道了类其实是能够创建出类实例的对象。好吧,事实上,类本身也是实例,当然,它们是元类的实例。
1
2
3
>>>
class
Foo(
object
):
pass
>>>
id
(Foo)
142630324
Python中的一切都是对象,它们要么是类的实例,要么是元类的实例,除了type。type实际上是它自己的元类,在纯Python环境中这可不是你能够做到的,这是通过在实现层面耍一些小手段做到的。其次,元类是很复杂的。对于非常简单的类,你可能不希望通过使用元类来对类做修改。你可以通过其他两种技术来修改类:
2) class decorators
当你需要动态修改类时,99%的时间里你最好使用上面这两种技术。当然了,其实在99%的时间里你根本就不需要动态修改类