写在篇前

  这篇文章主要介绍python3中metaclass(元类)的概念及其应用。简而言之,元类就是创建类的东西,而type就是Python的内建元类。如此一来,在python中,先要定义元类,然后利用元类创建类,最后根据类创建实例。

一切皆为对象

  相信任何一个python爱好者,从业者都听过"在python中,一切皆为对象"这句话,但为什么探讨元类前要先抛出这个话题呢?因为我觉得只有当你能够理解元类不仅是类也是对象,那么这才算是真正理解了"一切皆为对象",基本数据类型、集合、类等在python中都是对象。

  • 基本数据类型、集合是对象

      基础数据类型如数值类型int、float、bool、complex等都是对象,举例:

    >>> a = 1
    >>> b = 1.0
    >>> c = False
    >>> d = 1+2j>>> type(a)
    <class 'int'>
    >>> type(b)
    <class 'float'>
    >>> type(c)
    <class 'bool'>
    >>> type(d)
    <class 'complex'>
    

      集合映射类型如list、tuple、dict、set等也同样是对象,举例:

    >>> a = [1,2]
    >>> b = (1,2)
    >>> c = {1:2}
    >>> d = {1,2,2}
    # 试着自己用type试试吧
    # 注意使用type(a)和a.__class__是等效的;
    

      而其他数据类型如str、None毫无疑问,同样也是对象,大家可以自己试试,尝试他们的用法。

  • 函数、类是对象

      函数和类在python中又被称为第一类对象,第一类对象具有以下特性:

    • 可以赋值给一个变量
    • 可以作为元素添加到集合对象中
    • 可以作为参数值传递给函数
    • 可以作为函数的返回值

    举个栗子,如函数可以赋值给一个变量,当函数赋值给另外一个变量时,函数并不会被调用,仅仅是在函数对象上绑定一个新的名字而已。

    >>> def hello_world():
    ...     print('hello world!')
    ...
    >>> hello = hello_world
    >>> hello()
    hello world!
    

      同理,你还可以把该函数赋值给更多的变量,唯一变化的是该函数对象的引用计数不断地增加,本质上这些变量最终指向的都是同一个函数对象。

    >>> id(hello)
    4440775400
    >>> id(hello_world)
    4440775400# 查看一个对象的引用计数
    >>>import sys
    >>> sys.getrefcount(hello_world)
    3  # 可以思考一下为什么是3
    

      容器对象(list、dict、set等)中可以存放任何对象,包括整数、字符串,当然函数、类也可以作存放到容器对象中,例如:

    >>> class A(object):
    ...     pass
    ...
    >>> class B(A):
    ...     pass
    ...
    >>> list_cls = [A,B]
    >>> for cls in list_cls:
    ...     print(cls)
    ...
    <class '__main__.A'>
    <class '__main__.B'>
    

      而将函数、类作为参数或返回函数(类)最经典的栗子当然是装饰器啦~所以这里就不展开举例了,如果你有兴趣,可以阅读我之前的博客python装饰器详细剖析。

  • type、object也是对象

      python面向对象中,type和object的关系可能是最难理解的部分,既然比较难理解,不如就从难以理解的地方开始,请看以下代码:

    >>> isinstance(type,object)
    True
    >>> isinstance(object,type)
    True
    

      我想此时此刻,基本上的人都蒙圈了,what f**k?! type是object类的实例,object是type类的实例,这好像有点反人类啊!Anyway,我们来慢慢理清它们之间的关系:

    1. typeobject是类实例也是类

      首先看object,以下代码说明object对象既是object类的实例,也是type类的实例,并且在继承关系上,object没有父类。

      # object是作为一个对象
      >>> object
      <class 'object'>
      >>> object.__class__  # 同type(object)<class 'type'>  # 这个object类又是 type类的实例
      

      object是作为一个类

      >>> object.__bases__
      

      所以可知有以下关系

      isinstance(object,type)True
      isinstance(object,object)True
      

      再看type,首先type也是一个类,同时:

         # type是作为一个对象>>> type<class 'type'>>>> type.__class__<class 'type'># type是作为一个类>>> type.__bases__(<class 'object'>,)# 所以可知有以下关系>>> isinstance(type,object)True>>> isinstance(type,type)True
      

      综上所述,object和type是实例对象也是类,其中object是type类、object类的实例,type是type类、object类的实例,即:

         >>> isinstance(type,object)True>>> isinstance(type,type)True>>> isinstance(object,type)True>>> isinstance(object,object)True
      
    2. type继承自object

      issubclass(object,type)False
      issubclass(type,object)True
      

      可以看出,type作为一个类时,继承自object类,这一点可以从python的源码中得以窥见:

      class type(object):"""type(object_or_name, bases, dict)type(object) -> the object's typetype(name, bases, dict) -> a new type"""pass
      

        在这个类的注释中也可以看到,type有三种用法,其中第二种是我们最为熟悉的,其返回对象的类型,第一种动态创建类的方法和第三种元类的使用方法将会在后面探讨。

    3. type和object都是python oop的顶级

        object和type就好比"鸡生蛋、蛋生鸡"的关系,type继承自object,object是type的实例。怎么理解呢?在Python的世界中,在面向对象体系里面,存在两种关系:

      • 父子关系,即继承关系,表现为子类继承于父类。在python里要查看一个实例的父类,可以使用__bases__查看;
      • 类型实例关系,表现为某个类型的实例化,如小青是一条蛇,在python里要查看一个实例的类型,使用它的__class__属性可以查看,或者使用type()函数查看。

        而object是父子关系的顶端,所有的数据类型的父类都是它;type是实例类型关系的顶端,所有对象都是它的实例的。所以,type和object的关系如下图(请原谅我稚嫩的手稿):

 &emsp;&emsp;上图中,虚线代表类型关系、实线代表父子关系,b、B、A、type、object以及前面提到的基本数据结构、函数都是对象,从而让python是一门真正的面向对象的编程语言!

元类初探

  上面我们知道了type和object分别是python OOP两种不同关系的顶级。接下来我们继续看看,在类和对象的创建过程中,type和object分别扮演什么角色。我们知道,类的定义通过class关键字申明,一个类中可以有若干属性、方法:

class A(object):def say_hello(self):print("Hello World!")

  在上边的例子中,类Foo的创建过程中会执行class语句,此时:

  1. 首先确定元类,创建类对象:

    • 确定类Foo的父类是否有参数metaclass,如果没有则下一步;
    • 确定类Foo的父类的父类是否有参数metaclass,如果没有则下一步;
    • 使用默认元类type(type的用法会在3中讲解)

    因为这里我们似乎并没有定义什么元类,所以毫无疑问,会使用python默认元类type(type(object_or_name, bases, attrs))来创建类,其中object_or_name表示类名、bases表示父类、attrs表示属性和方法,用字典形式传入,所以以上类的定义等效于以下过程:

    >>> def say_hello():
    ...     print('Hello world!')
    ...
    >>> type('A', (object,),{'say_hello':say_hello})
    

  通过type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。动态语言本身支持运行期动态创建类,这和静态语言有非常大的不同,要在静态语言运行期创建类,必须构造源代码字符串再调用编译器,或者借助一些工具生成字节码实现,本质上都是动态编译,会非常复杂。

2、根据创建的类、创建对象

  我们知道,创建类的时候已经指明了类的元类是type、类的父类是object,因此此时,创建对象时会先根据type创建一个对象,然后再继承父类object的属性和方法。

元类进阶

  再一次说明实例、类和元类之间的关系(代码如下),在python中,先要定义元类,然后利用元类创建类,最后根据类创建实例。

>>> foo.__class__     # <class 'Foo'>
>>> Foo.__class__     # <class 'type'>
>>> type.__class__    # <class 'type'>

  前面提到,type是python默认是元类,那么可不可以自定义元类呢?答案当然是肯定的。自定义元类,可以改变对象的创建方式,满足一些特殊的需求。自定义的元类创建方式如下:

class ListMetaclass(type):"""元类就是重写新式类的实际构造方法__new__"""def __new__(cls, name, bases, attrs):attrs['add'] = lambda self, value: self.append(value)return super().__new__(cls, name, bases, attrs)# return type.__new__(cls, name, bases, attrs)# 注意理解以下输出
>>> ListMetaclass.__class__
<class 'type'>
>>> ListMetaclass.__bases__
(<class 'type'>,)
>>> ListMetaclass.__mro__
(<class '__main__.ListMetaclass'>, <class 'type'>, <class 'object'>)

  自定义元类需要继承type类或则type类的子类,然后再通过重写__new__方法,改变对象创建的方式。要使用自定义,只需要使用关键字metaclass指定元类即可。

class MyList(list, metaclass=ListMetaclass):passl = MyList()
l.add(1)
print(l)

注意:Python3中不再有__metaclass__属性以及模块级别的__metaclass__属性

  这样,Mylist作为list类的子类,既继承了list原有的方法appendinsert等,又获得了由元类的创建的add方法。

元类应用

  元类是一个很高深的东西,大多数场景我们并不需要它,这里必须要引用e-satis的一段话:

Metaclasses are deeper magic that 99% of users should never 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).

  关于元类的应用,最经典的应该属于ORM,以下一个关于ORM的例子来自廖雪峰python教程,略有改变,首先我们来看看ORM的调用形式,知道结果再来一步步实现ORM。

# _orm.py
from _orm_dataType import IntegerField, StringField
from _orm_model import Modelclass User(Model):# 定义类的属性到列的映射:id = IntegerField('id')name = StringField('username')email = StringField('email')password = StringField('password')# 创建一个实例
u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
# 保存到数据库
u.save()# print(User.__bases__,  # 父类
#       User.__class__,  # 元类
#       User.__dict__,  # 字典
#       u.__class__  # 所属类
#       )

  要实现该API调用,我们需要定义数据类型IntergerFieldStringField等等以及所有数据表的通用父类Model类。数据类型的定义很简单,我们只指明数据类型以及字段名就好:

#_orm_dataType.py
class Field(object):def __init__(self, name, column_type):self.name = nameself.column_type = column_typedef __str__(self):return '<%s:%s>' % (self.__class__.__name__, self.name)class StringField(Field):def __init__(self, name):super().__init__(name, 'varchar(100)')class IntegerField(Field):def __init__(self, name):super().__init__(name, 'bigint')

  接下来就是定义Model类,同时改变Model类的元类,使得Model子类的创建动态改变,这里需要注意的是,Model类继承自dict类,并且通过__getattr____setattr__改变了属性的默认读写方式。

# _orm_model.py
from _orm_dataType import Fieldclass ModelMetaclass(type):def __new__(cls, name, bases, attrs):print('*******__new__*******')# print(name)# print(bases)# print(attrs)# print('*******__new__*******')if name == 'Model':return super().__new__(cls, name, bases, attrs)# 这里是创建类,不是创建对象mappings = dict()for k, v in attrs.items():if isinstance(v, Field):mappings[k] = vfor k in mappings.keys():attrs.pop(k)attrs['__mappings__'] = mappings  # 保存属性和列的映射关系attrs['__table__'] = name  # 假设表名和类名一致return super().__new__(cls, name, bases, attrs)# 这里是创建类,不是创建对象# 指定新的meataclass
class Model(dict, metaclass=ModelMetaclass):def __init__(self, **kw):super().__init__(**kw)def __getattr__(self, key):try:return self[key]except KeyError:raise AttributeError(r"'Model' object has no attribute '%s'" % key)def __setattr__(self, key, value):self[key] = valuedef save(self):fields = []params = []args = []for k, v in self.__mappings__.items():fields.append(v.name)params.append('?')args.append(getattr(self, k, None))sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))print('SQL: %s' % sql)print('ARGS: %s' % str(args))if __name__ == '__main__':m = Model()

python meataclass详解相关推荐

  1. python区块链开发_Fabric区块链Python开发详解

    Hyperledger Fabric是最流行的联盟区块链平台.Fabric区块链Python开发详解课程 涵盖Fabric区块链的核心概念.Fabric网络搭建.Node链码开发.Python应用开发 ...

  2. python装饰器setter_第7.27节 Python案例详解: @property装饰器定义属性访问方法getter、setter、deleter...

    上节详细介绍了利用@property装饰器定义属性的语法,本节通过具体案例来进一步说明. 一.    案例说明 本节的案例是定义Rectangle(长方形)类,为了说明问题,除构造函数外,其他方法都只 ...

  3. 【python】详解类class的继承、__init__初始化、super方法

    原文链接; https://blog.csdn.net/brucewong0516/article/details/79121179?utm_medium=distribute.pc_relevant ...

  4. python与golang_Golang与python线程详解及简单实例

    Golang与python线程详解及简单实例 在GO中,开启15个线程,每个线程把全局变量遍历增加100000次,因此预测结果是 15*100000=1500000. var sum int var ...

  5. python 最小二乘法_最小二乘法及其python实现详解

    最小二乘法Least Square Method,做为分类回归算法的基础,有着悠久的历史(由马里·勒让德于1806年提出).它通过最小化误差的平方和寻找数据的最佳函数匹配.利用最小二乘法可以简便地求得 ...

  6. 【python】详解multiprocessing多进程-Pool进程池模块(二)

    [python]详解multiprocessing多进程-process模块(一) [python]详解multiprocessing多进程-Pool进程池模块(二) [python]详解multip ...

  7. 【python】什么是序列,Python序列详解

    什么是序列,Python序列详解 概述 序列索引 序列切片 序列相加 序列相乘 检查元素是否包含在序列中 序列相关的内置函数 range 快速初始化数字列表 概述 所谓序列,指的是一块可存放多个值的连 ...

  8. python多线程详解 Python 垃圾回收机制

    文章目录 python多线程详解 一.线程介绍 什么是线程 为什么要使用多线程 总结起来,使用多线程编程具有如下几个优点: 二.线程实现 自定义线程 守护线程 主线程等待子线程结束 多线程共享全局变量 ...

  9. Python线程详解

    Python线程详解 线程简介 开启多线程 线程之间共享 GIL全局解释器锁 线程间通信 线程简介 线程,有时被称为轻量进程(Lightweight Process,LWP),是程序执行流的最小单元. ...

最新文章

  1. 团队项目第一次冲刺 第二天
  2. C语言 文件操作10--配置文件读写
  3. Web 2.0背后的长尾理论
  4. 51单片机 自动重装载值计算
  5. 白话tensorflow分布式部署和开发
  6. 关于liaoxuefeng的python3教程实战第四天
  7. 计算机基础知识_计算机基础知识汇总
  8. 浮动元素引起的问题和解决办法
  9. 关于数据库查询中的几种连接
  10. 数据库原理及应用教程课后习题答案 第4版 微课版 陈志泊主编
  11. 修改表字段长度sql
  12. 100道接口测试面试题收好了!【建议收藏】
  13. .net framework 3.5win10无法安装,一招解决win10无法安装.NET Framework 3.5
  14. 2020年低压电工考试试卷及低压电工复审考试
  15. word文档中实现目录索引中标题加粗,前导符和页码不加粗
  16. 顺丰速运中山php招聘信息_中山顺丰快递招聘
  17. 作宾语,不定式与动名词的区别——脑动词和手动词
  18. Hive 2.3.4 Name node is in safe mode. The reported blocks xxx has reached the threshold 0.9990 of to
  19. VUE实现页面局部刷新
  20. 其实,我喜欢你很久了。

热门文章

  1. 数据仓库与联机分析处理技术
  2. Mining Precision Interface From Query Logs -- 学习笔记(一)
  3. 享元模式在JDK源码中的应用——Java设计模式系列学习笔记
  4. Leecode 869. 重新排序得到 2 的幂——Leecode每日一题系列
  5. 【一遍过!!!】1014 Waiting in Line (30 分)(题意+分析)
  6. 16行代码AC——紫书| 例题7-3 Fractions Again?! (UVA - 10976)_时间复杂度O(n)
  7. linux 命令 cd -p,Linux_实例讲解Linux中cd命令切换目录的使用技巧,cd命令大家再熟悉不过了,bash sh - phpStudy...
  8. 华为swot分析2020_2020华为音频产品汇总分析,完善产品质量,丰富产品线
  9. OSPF简单多区域及末梢区域配置
  10. eclipse3.2配置开发C/C++