python meataclass详解

写在篇前

  这篇文章主要介绍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 type
          type(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):
    pass

l = 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 Model


class 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 = name
        self.column_type = column_type

    def __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 Field


class 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] = v
        for 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] = value

    def 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()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值