V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
sergiojune
V2EX  ›  Python

再有人问什么是元类,就把这篇文章扔给他!

  •  
  •   sergiojune · 2018-08-16 08:55:28 +08:00 · 2335 次点击
    这是一个创建于 2052 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我之前在深入理解 python 中的类和对象中说过,python 中的类也是一个对象,可以说是类对象,可以由type来创建类对象的。有了这个知识我们先看看下面这个函数:

    这个可以根据我们传递的参数来指定生成相关的对象,可以说是很简单地动态创建类,我们再看看结果:

    这里输入的参数为 user,所以最好的结果就是生成了 User 这个类,这个方法虽然是简单,但是当里面类的逻辑越来越多的时候这个类里写的代码就会非常地多,就会变得很臃肿了,不好看,所以这种方法不合适。

    我们再看看 type() 这个方法,之前说它可以动态创建一个类,那我们现在看看到底怎样创建?

    在 pycharm 编辑器上,进入 type() 的内部源码查看,可以看到:

    第三行说的就是创建一个新类,接收的是三个参数,通过名字很容易就知道这三个参数的意义:

    • name:类的名称

    • bases:基类,就是所需要继承的类

    • dict:类的属性,包括方法

    那既然知道了就创建一个来看看。

    这个可以看到我创建了一个 person 的类,没有基类,注意基类这个接收的是一个元组,属性只有一个 name 属性。这个是不是很简单很直接,如果需要增加方法,也只是在 dict 参数加上对应的方法名即可,如下:

    我们知道能创建类的类就是元类,所以说type也是一个元类。这个还比较简单,因为就三个参数,按照规则来就可以了,但是这个只能是动态生成类,不能对类的生成过程做操作,也就是说不能控制类是如何生成的,所以在 python3 中还有个元类:metaclass,这个也是可以动态创建类的,比 type这个方法能操作的东西多了,但同时也有点难。

    在说 metaclass 之前,先说下类是如何生成的,类分两种。

    1. 普通的类,不通过 metaclass 来创建的,这个就简单,就是通过 type 来创建类对象。

    2. 第二种,就是使用 metaclass 的,这时就通过 metaclass 来创建类,如果此类没有,就会去寻找父类的 metaclass,再没有,还是会继续往上找,直至找到。

    再说下为什么要使用 metaclass 来创建类呢?

    • 将创建类对象的过程委托给元类来做,不需要再在类内部来操作,这样代码的分离性比较强

    • 可以检查该类有没有实现父类的那些抽象方法,没有重载的话就可以直接抛异常,不让创建成功

    还有很多,以后见到再补充说明,还有就是你会见到很多框架都会使用metaclass 来创建类的,要想成为一名好的 python 工程师,元类这一关必须过的。

    说了,那么多,举个小栗子来说明怎么使用 metaclass 来创建类吧!

    可以看到,在类中指明 metaclass 来控制类的生成,这时所指向 metaclass 必须继承 t ype 这个类才可以。我们还在 metaclass 这个类中通过修改__new__这个方法来控制类的实现,这时就可以将__init__和__new__这两个方法分离出来了。

    下面再来通过实现一个 orm 框架类体现通过元类创建类的好处。因为大家都知道在 python 中使用 pymysql 这个库来操作 mysql 是很烦,所以才会有了这个 orm 框架,这里引用下廖雪峰的官网的一段话:

    ORM 全称“ Object Relational Mapping ”,即对象-关系映射,就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码更简单,不用直接操作 SQL 语句。

    要编写一个 ORM 框架,所有的类都只能动态定义,因为只有使用者才能根据表的结构定义出对应的类来。

    talk is cheap show you the code.

    这个是我们在使用 orm 框架时希望是上面这样调用的,这里就简单定义两个字段 name 和 age,User 类中还有个内部类是 Meta ,这里面用了定义数据表的其他属性,与字段定义分开,所以里面定义了一个数据表名称。在使用 save() 方法保存的时候就是内部拼接 mysql 语句,等下实现。

    接下来先对两个字段类实现。

    这个是对 IntField 类的实现,可以看到里面很多逻辑,同时 CharField 这个类也是这样实现的。

    接下来是对 metaclass 的实现

    这个__new__的方法就是对类的生成的控制,我们可以断点看看,里面的参数是什么

    可以看到,经过拆包,args 可以分为三个参数,一个类名称,另一个为元组,就是基类,还有一个就是类的属性,所以可以把上面的参数改为下面的更好操作。

    下面我们需要对 attrs 的参数里的字段进行抽取出来,但是在判断的时候需要判断的东西很多,比如需要判断是 CharField 或者 IntField 才进行抽取,这样如果字段多了的话写的代码就会很多,有一个技巧就是让所有的字段都共同继承一个基类 Field,然后判断是不是这个基类即可

    这个元类基本上是完成了,最后记得调用父类的__new__进行返回,要不然会创建类对象失败,从而调用不了__init__方法来实例对象。

    这里还有一个问题,就是用户在创建 User 时候可能会直接存入字段信息,就比如下面这个:

    user = User('张三', 23)  
    

    所以我们还需要在 User 类中实现 __init__ 方法,但是如果直接在 User 类中添加这个方法的话就不太好看了,我们可以再实现一个基类 BaseModel,在这个类中添加 __init__ 方法,这样就比较好,而且还可以实现 save() 这个方法。

    在使用了另一个基类 BaseModel 之后,将这个基类来用 metaclass 来实现,同时 User 就不需要实现 metaclass 了,只需要继承此基类就好,因为 meta class 会向上查找,只需要父类实现就可以了。同时,在元类 Model 中,我们还需要加上一个判断,只有在 User 这个类创建时才需要控制其类的生成,其他的就不需要了。

    这时再看看基类 BaseModel 的 __init__ 方法如何实现的。

    通过 setattr() 方法来进行赋值就简单多了,不需要一个一个判断完再取出来。

    剩下的就只有 save 方法没有实现了,这个方法就简单多了,只需要实现拼接 mysql 语句就可以了,这里需要拼接的语句是

    sql = 'insert user(name, age) values ("", 23)'  
    

    再看看代码实现

    以上就是整个 orm 框架的实现了,是不是看起来很简单?却解决了 繁杂的 mysql 操作语句。

    最后运行下就会看到一个完整的 mysql 语句出现。

    如果我们在需要添加别的类型字段的话就只需要实现下这个类就好,其他的都不需要管了,是不是超级方便的?

    写在最后

    如果看不不懂得话建议多敲代码几篇,然后打上断点跟着代码一段一段思考,这样子就会好理解多了,还有就是看了很多遍还是不懂的话可以先放下,毕竟有 99% 的时候都不需要用这个元类,因为实在是太麻烦了,等以后回来再看也不迟。

    完整代码公众号后台回复「元类」获取

    ps:原创不易,如果文章对你有用的话,点赞留言转发是对我的最大支持!

    日常学 python

    代码不止 bug,还有美和乐趣

    5 条回复    2018-08-16 16:41:08 +08:00
    qyzxgl
        1
    qyzxgl  
       2018-08-16 09:11:02 +08:00
    感谢,看明白了
    Alexc
        2
    Alexc  
       2018-08-16 09:39:13 +08:00
    一个标题都抄来抄去
    abmin521
        3
    abmin521  
       2018-08-16 10:04:44 +08:00 via Android
    我懒得扔 doc.python.org 自己去看
    ivechan
        4
    ivechan  
       2018-08-16 10:36:20 +08:00   ❤️ 1
    luopengfei14
        5
    luopengfei14  
       2018-08-16 16:41:08 +08:00
    太长,不看🙈
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3785 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 10:21 · PVG 18:21 · LAX 03:21 · JFK 06:21
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.