metaclass小结

先前学习看到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环境中这可不是你能够做到的,这是通过在实现层面耍一些小手段做到的。其次,元类是很复杂的。对于非常简单的类,你可能不希望通过使用元类来对类做修改。你可以通过其他两种技术来修改类:

1) Monkey patching

2)   class decorators

当你需要动态修改类时,99%的时间里你最好使用上面这两种技术。当然了,其实在99%的时间里你根本就不需要动态修改类

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。