大多数情况下,我们不需要用到元类(元编程),但是在一些及特殊的时候,比如我需要控制类的创建行为时,就需要使用元编程了,本文详细介绍元编程思想
一切皆对象
开始学习元类,我们需要先回顾一些必备的基础知识,首先是一切皆对象这个概念,在学习Python这门语言之初,我们就已经知道,Python中一切皆对象。
基础数据类型
1 | # 字符串是str类的对象 |
函数和类
1 | # 函数是function的对象 |
int,str,function是谁的对象?
1 | # type是一切对象的类,即元类。在Python中所有对象都是type这个类的对象 |
以上就是关于一切皆对象的简单回顾。
什么是元编程
元编程是Python中非常重要的一个知识点,它使我们可以在对象的创建过程中加入一些自定制的,项目中实际需要的信息。
比如产品经理要我们根据用户手机壳颜色更换主题这种需求,也就是说官方提供的方法不能满足需求的时候,我们需要自定制对象的创建。
内置方法__call__
想要搞清楚元编程,我们还需要回顾另外一个知识点,内置方法__call__
。我们之前学过几个内置方法,先简单回顾一下,再来看看__call__
方法。
__getattribute__
1 | # 对象的属性访问会触发__getattribute__方法执行 |
__getitem__
1 | # 如果希望实现对象的self[key]访问方式,可以自定义__getitem__方法 |
__call__
1 | # 如果对象实现了__call__方法,则可称之为可调用对象 |
小节一下,对象加括号u(), 就会执行该对象的类中的__call__
方法,请记住这句话
有哪些可调用对象
函数,类,函数加括号,比如
1 | def foo(): |
那么类呢?
1 | class Bar: |
自定制基础对象的创建
在本文开始前,我提到,特殊的需求下,我们需要自定制对象的创建,但是简单的小程序,是不需要这样做的,最多自定制基础对象的创建即可,比如,我希望创建的字符串对象是可调用对象,请看
1 | # 原生的字符串对象是不具备可调用性的,我们需要自定制字符串对象 |
或者,比如我希望我希望字符串实现hhh方法
1 | class MyStr(str): |
使用class关键字创建对象
讲到这里,我们已经知道了,在小型程序中,Python提供给我们的工具(基础数据类型)已经足够使用了,最多,我们可以自定制(通过继承的方式)基础数据类型来实现复杂需求,但是这些数据类型无法描述我们丰富多彩的世界,无法满足产品经理日益增长的疯狂的想法,因此我们需要使用class关键字来描述更加丰富、实际的世界。class登场了。举例,我要描述黑哥这个人,他有哪些属性和方法呢?
1 | class HeiGe: |
使用类来描述丰富多彩的世界使我们多了一个工具,面向对象编程通过使用继承、封装、多态这几个特性是我们编程更加得心应手。
自定制类对象的创建
通过前面的回顾我们知道,对象加括号会执行对象所属类的__call__
方法,我们还知道,类也是对象,Python在执行我们的代码时,当解析到class关键字,就会将该语句解析成 xxx = 元类()。所有创建类这个对象时,实际上执行了元类的__call__
方法。
基于这一点,我们正式开始讨论元编程(终于到正题了)。
__new__
Python中一切对象的创建都是通过__new__
来实现的,不管是字符串、整型、字典、列表、函数、类(特殊,可以创建对象)。
对象的创建过程是这样的,我们使用类来举例(使用class关键字时创建的对象):
执行代码时遇到class关键字—>执行元类的__call__
方法(大部分情况下是type)—>调用__new__
方法创建对象—>调用__init__
方法保存数据。
1 | # -*- coding: utf-8 -*- |
自定制类对象的创建
如果想自定制类的创建可以自定义一个元类继承type,然后重写__new__
方法。
自定制实例对象的创建
如果想自定制类对象的创建, 使用metaclass=MyMetaClass, 然后重写__new__
方法。
创建对象的本质
总结
Python中的一切都是对象,它们要么是类的实例,要么是元类的实例,除了type。type实际上是它自己的元类,在纯Python环境中这可不是你能够做到的,这是通过在实现层面耍一些小手段做到的。其次,元类是很复杂的。下面的内容来自这篇文章,详情请点击链接。
究竟为什么要使用元类?
现在回到我们的大主题上来,究竟是为什么你会去使用这样一种容易出错且晦涩的特性?好吧,一般来说,你根本就用不上它:
“元类就是深度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。” —— Python界的领袖 Tim Peters
元类的主要用途是创建API。一个典型的例子是Django ORM。它允许你像这样定义:
1 | from django.db import models |
但是如果你像这样做的话:
1 | joe = UserInfo(name='Joe', age='35') |
这并不会返回一个IntegerField对象,而是会返回一个int,甚至可以直接从数据库中取出数据。这是有可能的,因为models.Model定义了metaclass, 并且使用了一些魔法能够将你刚刚定义的简单的Person类转变成对数据库的一个复杂hook。Django框架将这些看起来很复杂的东西通过暴露出一个简单的使用元类的API将其化简,通过这个API重新创建代码,在背后完成真正的工作。
后面,我将带领大家利用元编程来自定义自己的orm框架。