How does it work - with_metaclass

我在看源代码的时候,经常蹦出这一句:How does it work!竟然有这种操作?本系列文章,试图剖析代码中发生的魔法。顺便作为自己的阅读笔记,以作提高。

先简单介绍下Python中的元类(metaclass)。元类就是创建类的类,对于元类来说,类是它的实例,isinstance(cls, metaclass)将返回True。Python中的所有类,都是type的实例,换句话说,type是元类的基类。使用type创建一个类的方法如下:

>>>type('MyClass', (), {})

<class '__main__.MyClass'>type接受三个参数,第一个参数是类名称,第二个参数是继承的基类的元组,第三个参数是类的命名空间。上例中,我们创建了一个无基类(直接继承object),无初始命名空间的类MyClass。

注:使用type创建的类和使用元类的类,都是新式类

使用元类后,该类将由定义的元类实例化来创建。定义的方法在Python 2与Python 3中有所不同:


# Python 2:
class MyClass(object):
    __metaclass__ = MyMeta

# Python 3:
class MyClass(metaclass=MyMeta):
    pass

如果你的项目需要兼容Python 2和Python 3,就需要使用一种方法,同时支持Python 2和Python 3。元类有两个基本特性:

元类实例化得到类
元类能被子类继承
根据这两个特性,我们不难得到解决方案:

用元类实例化得到一个临时类
定义类时继承这个临时类
我们可以写出一个with_metaclass函数:


def with_metaclass(meta, *bases):
    """Compatible metaclass

    :param meta: the metaclass
    :param *bases: base classes
    """
    return meta('temp_class', bases, {})

# Testing:
class TestMeta(type):
    def __new__(cls, name, bases, d):
        d['a'] = 'xyz'
        return type.__new__(cls, name, bases, d)


class Foo(object):pass

class Bar(with_metaclass(TestMeta, Foo)): pass

我们就创建了一个以TestMeta为元类,继承Foo的类Bar。验证:

>>> Bar.a
'xyz'
>>> Bar.__mro__
(<class '__main__.Bar'>, <class '__main__.temp_class'>, <class '__main__.Foo'>, <class 'object'>)

一切正常,但我们看到在Bar的mro里混进了一个临时类temp_class,你忽略它吧,有时会很麻烦。作为完美主义者,我想寻找一种解决办法,不要在mro中引入多余的类。

Python的six模块专门为解决Python 2to3兼容问题而生,模块里带有一个with_metaclass函数,我们来看它是怎么实现的:(为了debug,添加了一个print语句)


def with_metaclass(meta, *bases):
    class metaclass(type):
        def __new__(cls, name, this_bases, d):
            print(cls, "new is called")
            return meta(name, bases, d)
    return type.__new__(metaclass, 'temp_class', (), {})

# Testing:
class TestMeta(type):
    def __new__(cls, name, bases, d):
        d['a'] = 'xyz'
        print(cls, "new is called")
        return type.__new__(cls, name, bases, d)

一时看不懂?没关系,我们来用用看,为了看清楚过程,我们分成两步执行:

>>> temp = with_metaclass(TestMeta, Foo)
>>> class Bar(temp): pass
...
<class '__main__.with_metaclass.<locals>.metaclass'> new is called
<class '__main__.TestMeta'> new is called
>>> Bar.a
'xyz'
>>> Bar.__mro__
(<class '__main__.Bar'>, <class '__main__.Foo'>, <class 'object'>)

我们明明生成了一个临时类temp_class,但后来竟然消失了!下面来仔细分析函数的运行过程。首先我们看到,执行第一步生成临时类时,两个__new__都没有调用,而第二步定义类时,两个__new__都调用了。奥秘就在函数的返回语句return type.__new__(metaclass, 'temp_class', (), {}),它创建了一个临时类,具有如下属性:

名称为temp_class
是函数内部类metaclass的实例,它的元类是metaclass
没有基类
创建时仅调用了type的__new__的方法
这是一个metaclass实例的不完全版本。接下来,定义Bar时,Bar得到继承的元类metaclass,过程如下:

实例化metaclass
调用metaclass.__new__
返回meta(name, bases, d), meta=TestMeta,bases=(Foo,)
调用TestMeta.__new__实例化得到Bar
Bar的基类由第3步得到,于是就去除了temp_class,这其实用到了闭包,with_metaclass返回的临时类中,本身无任何属性,但包含了元类和基类的所有信息,并在下一步定义类时将所有信息解包出来。

以上就是with_metaclass源代码的解析,通过这篇文章,相信能加深元类与闭包的理解。


寻寻觅觅
认真写博客
48 声望
6 粉丝
0 条评论
推荐阅读
socket close与shutdown的区别
Callingcloseandshutdownhave two different effects on the underlying socket.

icheeringsoul阅读 1.4k

基于Sanic的微服务基础架构
使用python做web开发面临的一个最大的问题就是性能,在解决C10K问题上显的有点吃力。有些异步框架Tornado、Twisted、Gevent 等就是为了解决性能问题。这些框架在性能上有些提升,但是也出现了各种古怪的问题难以...

jysong6阅读 3.9k评论 3

滚蛋吧,正则表达式!
你是不是也有这样的操作,比如你需要使用「电子邮箱正则表达式」,首先想到的就是直接百度上搜索一个,然后采用 CV 大法神奇地接入到你的代码中?

良许4阅读 2.3k

又一款眼前一亮的Linux终端工具!
今天给大家介绍一款最近发现的功能十分强大,颜值非常高的一款终端工具。这个神器我是在其他公众号文章上看到的,但他们都没把它的强大之处介绍明白,所以我自己体验一波后,再向大家分享自己的体验。

良许5阅读 1.8k

FastAPI性能碾压Flask?
不止一次的听过,FastAPI性能碾压Flask,直追Golang,不过一直没有测试过,今天闲着没事测试一下看看结果。不知道是哪里出了问题,结果大跌眼镜。

二毛erma02阅读 10.2k评论 3

封面图
Python之如何优雅的重试
为了避免偶尔的网络连接失败,需要加上重试机制,那么最简单的形式就是在对应的代码片段加一个循环,循环体里使用异常捕获,连接成功时退出循环,否则就重复执行相关逻辑,此时修改之后的函数f如下

Harpsichord12073阅读 7.3k

Linux终端居然也可以做文件浏览器?
大家好,我是良许。在抖音上做直播已经整整 5 个月了,我很自豪我一路坚持到了现在【笑脸】最近我在做直播的时候,也开始学习鱼皮大佬,直播写代码。当然我不懂 Java 后端,因此就写写自己擅长的 Shell 脚本。但...

良许1阅读 2.1k

48 声望
6 粉丝
宣传栏