美文网首页
Django源码中的metaclass使用是如何兼容Python

Django源码中的metaclass使用是如何兼容Python

作者: Python资源收集狂 | 来源:发表于2018-03-26 12:20 被阅读0次

    之前看Django源码时没太注意metaclass是怎么做的2跟3的兼容,直到看见Django2.0dev版中只是用了Python3.x中metaclass的使用方式。

    Django源码

    某读者曰:喂喂,博主你上次写带那篇《如何阅读Django源代码-上篇(the5fire版) 》是不是太监了。。。

    the5fire曰:不会的,下篇写到一半觉得没啥用,所以没继续写,啥时候有心情了会发出出来,让大家批评的。你瞧,这篇文章不也是个例证吗。阅读源码的几个入手点之一。

    Django2.0开始不再兼容Python2.x了,因此Django2.0dev中的关于metaclass使用的代码是这样的:

    点击查看源码

    这是Python3.x中关于metaclass的使用,在Python2.x中,我们知道metaclass的使用是需要通过__metaclass__的定义来实现的,示例代码如下:

    在Python2.x和Python3.x之间,关于metaclass的使用,已经是完全不兼容了,从语法层面。那么问题来了,Django2.0之前的版本是如何做到兼容的???

    我们来看下Django1.11中这部分代码是怎么写的,github - django源码:

    这个six.with_metaclass又是什么鬼呢?

    嗯,看起来有点复杂,不过不要紧,接下来我们慢慢捋一捋。

    先来理解__new__这个魔术方法

    如果你实现过单例模式,那么你一定用过它了:

    从这里我们能看到,__new__是用来创建实例(instance)的,我们在这可以控制创建出什么新的实例还是复用已有的实例。但是我们可以改下__new__的返回值。

    这种情况下得到的是Foo这个类,而不是实例。因此我们可以通过在__new__这个方法里折腾点东西。

    这里总结下,__new__是来控制类创建的,而__init__是来控制实例初始化的。

    理解type

    再来看type的使用,the5fire之前写的那篇《Django分表的两个方案》有说到怎么使用type动态创建类。可以通过这案例理解type的使用。而这里的type.new是类似的,我们在ipython中看下这俩函数的说明:

    type(name, bases, dict) 返回一个新的类型,继承自传递进入的bases(这个参数必须是tuple类型),以及拥有dict参数中定义的属性。 type.__new__(S, ...) 返回一个S类型的新对象,注意,这个新对象并不是我们平时写代码中的类的实例,而是类。因为S必须是type的子类(继承自type)。

    我们还是来通过代码认识下:

    把上面代码放到本地,运行下试试。

    其中[3]里面定义了CustomClass.__new__,你运行过上面代码后发现并没有print出来"in CustomClass"的结果。这意味着通过type.__new__创建的CustomClass的实例,并没有执行__new__这个方法。

    我们继承下创建出来的这个Person试试:

    为什么定义的时候就会输出"in CustomClass",也就是执行CustomClass.__new__呢,而不是像上面那样,在实例化的时候才执行?这就涉及到类创建的过程了,也就是metaclass的部分。我们稍后介绍,先来看关于type、类、实例的关系,可以从这个简略图理解下:

    对应到上面第[3]部分的代码就是,我们通过type.__new__创建了CustomClass的实例(是一个类)Person,所以可以通过GoodPerson继承Person。

    理解metaclass

    理解了type之后,我们再来看metaclass的使用。我们以Python2.x的方式来解释。先看个示例代码:

    你可以把这个代码copy到你电脑上的test_meta.py文件中,然后用Python2.7运行下。会得到如下输出:

    也就是说,当类定义被执行的时候,就会开始执行metaclass中的方法了,这个方法的作用是创建类。那么问题又来了,大部分情况下我们并不会定义__metaclass__,那类是怎么创建的呢?还是甩文档出来:

    这个__metaclass__你可以理解为Python暴露给我们的一个接口,用来自己实现创建类的过程。在这一过程中我们可以操作即将生成的类,比如上面的代码中,metacls.__new__里面的attrs['name'] = 'the5fire'这行代码,直接增加一个类级变量。当然也可以在这里增加方法的定义。

    理解到这之后,再回过头来看我们上面的逻辑,我把完整代码copy到这:

    我们解答上面的问题:

    这是因为类的定义(或者说创建)的过程,是通过__metaclass__来完成的,虽然我们没有显示的定义__metaclass__,但是基于下面的规则,会把我们定义的class CustomClass作为metaclass,来创建类。

    metaclass查找规则:如果当前类没有__metaclass__,但是有至少一个基类,那么会去使用第一个基类的__class__作为__metaclass__,如果没有__class__则会使用type来创建类。

    上面的例子中,也就是GoodPerson没有查找到__metaclass__的定义,那么就会看是否存在Person.__class__,如果存在,就把它作为__metaclass__。

    对应的官网文档如下:

    Python3的说明没找到,但是可以看源码:https://github.com/python/cpython/blob/master/Python/bltinmodule.c#L108 。其中的base0->ob_type可以理解为base.__class__也就是父类的类型。

    再来看Django的six.with_meta代码

    有了上面的认识,我们再来看Django中关于metaclass在Python2和Python3中兼容的处理就很好理解了。也就是除了在Python2中,通过:

    或者Python3.x中通过:

    的定义之外,还有另外的方法来指定metaclass,也就是上一节我们讲到的。

    再来回顾下Django这部分代码,把代码合在一起:

    这代码转换下就是:

    看起来似乎没有问题,But,为啥需要两个metaclass: metaclass和ModelBase。是不是可以改成这样:

    问题就是我注释地方支持的问题, 这种方式指定了metaclass之后,就无法设置子类了。

    所以我们就能明白这段代码中,*bases参数,以及the5fire注释的那个位置的bases的作用了。

    参考

    Customizing class creation - Python2.x Docs

    Customizing class creation - Python3.x Docs

    what is metaclass in python

    为啥不能用object.__new__替代type.__new__

    《Django分表的两个方案》

    - from the5fire.com 

    文章转自:https://www.the5fire.com/

    相关文章

      网友评论

          本文标题:Django源码中的metaclass使用是如何兼容Python

          本文链接:https://www.haomeiwen.com/subject/thtrcftx.html