← 返回文章流

How does it work - with_metaclass

我在看源代码的时候,经常蹦出这一句:How does it work! 竟然有这种操作?本系列文章,试图剖析代码中发生的魔法。顺便作为自己的阅读笔记,以作提高。 先简单介绍下 Python 中的元类(metaclass)。元类就是创建类的类,对于元类来说,类是它的实例,isinstance(cls, metaclass)将返回True。Python 中的所有类,都是type的实例,换句话说,type是元类的基类。使用type创建一个类的方法如下: <!-- more --> >>> 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源代码的解析,通过这篇文章,相信能加深元类与闭包的理解。

来自 Frost's Blog作者主页
在原博客阅读全文 (新标签页打开)

以上内容来自博客订阅源,版权归原作者。关注作者后可以在 关注列表 第一时间查看作者文章。

一起聊聊 0

尊重不同观点

还没有评论。欢迎分享第一个想法。

RSS SUBSCRIPTIONS

选择 RSS 订阅

把博客汇的文章添加到你常用的阅读器。

关注文章 RSS 订阅本订阅需要登录后使用