资讯专栏INFORMATION COLUMN

Python Metaclass 初探

nidaye / 2237人阅读

摘要:以我们的程序为例,就是以为产生了一个名为的新类型,改类型的实现由给出,而就包含了通过返回的这个方法。从中找到这些类并一一执行测试。

先以一个大牛的一段关于Python Metapgramming的著名的话来做开头:

Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why). – Tim Peters

翻译一下:Metaclasses是99%的用户都无需费神的黑科技。如果你还在纠结你是不是需要它的话,答案是NO (真正需要的人根本不需要解释) - Tim Peters

这是什么鬼话?道可道,非常道吗?

Meta?

好,装B已毕。这确实是一个冷僻的,不常用的话题。一篇短文肯定讲不完。 所以叫做初探。

英文meta这个词其实是从希腊语里面借来的。wikipedia上的解释是:

indicate a concept which is an abstraction behind another concept, used to complete or add to the latter

不看还好,其实看了更晕。好在后面的解释有一句“更高一层的抽象”,可以帮助理解。 其实我们可以这样理解。meta的意思就是“关于什么的什么”:比如metadata可以理解为“关于数据的数据”,metaprogramming可以理解为“关于编程的编程”。这就和“更高一层的抽象” 比较契合了。同时又隐隐和编程中的另一个永恒主题-recursion联系在了一起。

另外,meta这个词天朝这边翻译成“元”,海峡对岸翻译成“后设”。其实我都不大理解从何而来。

实例

聚焦到我们今天的主题,metaprogramming就是编写用来生成代码的代码。

假设我们写了一个NB的函数,用来计算一个任意复杂的算数表达式的值:
像1+2, 3*6+10, 什么的都可以交给它去计算。这样的函数的算法不是我们的主题,所以我们请出python自带的大招eval(),一行就可以搞定了:

def calc(expression):
    return eval(expression)

因为输入的可能性是无限的,所以我们肯定要好好测试一下这个函数了。假定我们想了 上百个test case。又假定我们是用unittest这个module来做测试的。这样的测试程序一般会长成这样:

import unittest

class TestStringMethods(unittest.TestCase):

    def test_upper(self):
        self.assertEqual("foo".upper(), "FOO")

    def test_isupper(self):
        self.assertTrue("FOO".isupper())
        self.assertFalse("Foo".isupper())

    def test_split(self):
        s = "hello world"
        self.assertEqual(s.split(), ["hello", "world"])
        # check that s.split fails when the separator is not a string
        with self.assertRaises(TypeError):
            s.split(2)

if __name__ == "__main__":
    unittest.main()

所以我们的目的就是用metaprogramming的方式来自动产生类似上面的测试类。

先上程序后解释:

#!/usr/bin/python3
import unittest

def calc(expression):
    return eval(expression)

def add_test(name, asserts):
    def test_method(asserts):
        def fn(self):
            left, right = asserts.split("=")
            expected = str(calc(left))
            self.assertEqual(expected, right)
        return fn

    d = {"test1": test_method(asserts)}
    cls = type(name, (unittest.TestCase,), d)
    globals()[name] = cls

if __name__ == "__main__":
    for i, t in enumerate([
            "1+2=3",
            "3*5*6=90"]):
        add_test("Test%d" % i, t)
    unittest.main()

NB的calc()函数我们解释过了。main这段也比较简单:我们用声明的方式定义了一组测试,然后通过unittest来执行。

有点复杂的是add_test()。我们先来看看最内层的fn(self)这个方法。逻辑上,它就是把输入的测试用例分成两份,一份是calc()的输入,一份是我们期待的结果;然后调用calc(), 接着用assertEqual()来测试。

但是这个self有点奇怪 - 这里没有类,哪里来的self? 其实fn(self)确实是一个类的方法,只不过这个类是我们通过代码动态生成的。也就是下面这一行:

cls = type(name, (unittest.TestCase,), d)

这里的type()就是通常我们用来检查某个变量的类型的那个函数。只不过它还有另外一种不大为人知的形式:

class type(name, bases, dict)

这第二种形式,就会产生一个新的类型。以我们的程序为例,就是以unit.TestCase为baseclass, 产生了一个名为TestN的新类型,改类型的实现由d给出,而d就包含了通过closure返回的fn(self)这个方法。只不过在这个新类里面,它的名字叫做 test1()

最后,我们把这个新产生的类加入到当前全局符号表里面,也就相当于上面给出的unittest的例子。

所以,总结一下。当我们运行这个脚本的时候,这段比较短的代码会针对每一个测试的表达式产生一个新的测试类,并动态生成测试的方法加载到该类里面。unitestglobals中找到这些类并一一执行测试。

上面的例子中,其实一行一行手打calc(1+2) == 3也没什么大不了的。但是当你要表达的逻辑比较复杂的时候,metaprogramming的强大就体现出来了。

总结

那么,看完这篇文章,我们也成为Tim所说的1%的程序猿了!其实,也许他的意思是,99%的编程工作都用不到这样技巧。在一些特殊的场合,比如编写某种框架的时候,metaprogramming会做到事半功倍。祝你在实践中碰到这样的机会。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/40855.html

相关文章

  • [译]理解pythonmetaclass

    摘要:但我并不是一个翻译者并不会严格遵守每行每句的翻译有时候我会将表述换个顺序省略一些我认为无关紧要的话,以便读者更好理解。类也是对象在理解之前,我们先要掌握中的类是什么。这个对象类自身拥有产生对象实例的能力。不过这样做是为了保持向后兼容性。 前言 这篇博客是我在stackoverflow上看了一个提问回复后写的,例子基本用的都是e-satis本人的例子,语言组织也基本按照翻译来。 但我并不...

    liuchengxu 评论0 收藏0
  • How does it work - with_metaclass

    摘要:先简单介绍下中的元类。元类就是创建类的类,对于元类来说,类是它的实例,将返回。中的所有类,都是的实例,换句话说,是元类的基类。 我在看源代码的时候,经常蹦出这一句:How does it work!竟然有这种操作?本系列文章,试图剖析代码中发生的魔法。顺便作为自己的阅读笔记,以作提高。 先简单介绍下Python中的元类(metaclass)。元类就是创建类的类,对于元类来说,类是它的实...

    testbird 评论0 收藏0
  • python 类和元类(metaclass)的理解和简单运用

    摘要:什么是元类刚才说了,元类就是创建类的类。类上面的属性,相信愿意了解元类细节的盆友,都肯定见过这个东西,而且为之好奇。使用了这个魔法方法就意味着就会用指定的元类来创建类了。深刻理解中的元类 (一) python中的类 今天看到一篇好文,然后结合自己的情况总结一波。这里讨论的python类,都基于python2.7x以及继承于object的新式类进行讨论。 首先在python中,所有东西都...

    zhangqh 评论0 收藏0
  • Python: metaclass小记

    摘要:最前面那个,解释器实际的流程是解析这段代码,得知它需要创建一个类对象,这个类的名字叫做它的父类列表用表示是,它的属性用一个来表示就是。解决方法很简单关键就是前面被特别标记了的应当返回这个的父类的方法返回的对象。 (原发于我的blog:Python: metaclass小记 ) 友情提示:本文不一定适合阅读,如果执意要读,请备好晕车药。 题记 Metaclasses are deepe...

    mushang 评论0 收藏0
  • Python -- 元类metaclass详解

    摘要:原链接中的元类是什么类也是对象在理解元类之前,需要掌握中类概念。事实上,是中用于创建所有类的元类。类本身是元类的对象在中,除了,一切皆对象,一切都是类或者元类的对象。事实上是自己的元类, 学习契机 项目中使用Elasticsearch(ES)存储海量业务数据,基于ES向外提供的API进一层封装,按需处理原始数据提供更精确、更多样化的结果。在研究这一层的代码时接触到@six.add_me...

    tracy 评论0 收藏0

发表评论

0条评论

nidaye

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<