• python 元类详解


    原文链接:https://cloud.tencent.com/developer/news/397470

    1 简介

    在我阅读Django框架源代码的过程中碰到很多元类的实例,看起来很吃力很晦涩;在看python cookbook中关于元类创建单例模式的那一节有些疑惑。因此花了几天时间研究下元类这个概念。通过学习元类,我对python的面向对象有了更加深入的了解。

    2 type和object

    请下记住下面这两句话,后面再详细解释。

    object类是所有新式类的父类。

    type是所有类的类。

    那么type和object是什么关系呢?object是一个新式类,我们可以通过object.__class__和object.__bases__来获取object所属的类核他的父类。

    图1:object的类型

    这说明 object类是一个type元类的实例。这与type是所有新式类的类这一说法相符合。

    图2:object的继承关系

    这说明 object类已经处于继承链条的顶端,是所有类的父类。

    图3:type的类型

    这说明type自身的类就是type。就是说type元类也就是由type自身创建的。

    图4:type类型的基类

    type元类的父类是object。

    object类是由元类type创建的,但是type类又继承了object类。 type元类的类则是由type元类自身创建的。我们把python中的内置类和用户创建的内纳入其中,我们就可以画出一下关系图

    图5:type和object的关系

    3 类也是对象

    在理解元类之前,你需要先掌握Python中的类。在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。在Python中这一点仍然成立:

    图6: 类的描述

    但是,Python中的类还远不止如此。类同样也是一种对象。只要你使用关键字class,Python解释器在执行的时候就会创建一个对象。将在内存中创建一个对象,名字就是ObjectCreator。这个对象(类)自身拥有创建对象(类实例)的能力,而这就是为什么它是一个类的原因。但是,它的本质仍然是一个对象,于是你可以对它做如下的操作:你可以将它赋值给一个变量, 你可以拷贝它, 你可以为它增加属性, 你可以将它作为函数参数进行传递。

    图7:类对象实例

    4 元类

    通过上面的描述,我们知道了Python中的类也是对象。元类就是用来创建这些类(对象)的,元类就是类的类,你可以这样理解为:

    MyClass = MetaClass()    #元类创建

    MyObject = MyClass()     #类创建实例

    实际上MyClass就是通过type()来创创建出MyClass类,它是type()类的一个实例;同时MyClass本身也是类,也可以创建出自己的实例,这里就是MyObject函数type实际上是一个元类。type就是Python在背后用来创建所有类的元类。现在你想知道那为什么type会全部采用小写形式而不是Type呢?好吧,我猜这是为了和str保持一致性,str是用来创建字符串对象的类,而int是用来创建整数对象的类。type就是创建类对象的类。你可以通过检查__class__属性来看到这一点。Python中所有的东西,注意,我是指所有的东西——都是对象。这包括整数、字符串、函数以及类。它们全部都是对象,而且它们都是从一个类创建而来。

    __metaclass__属性

    你可以在写一个类的时候为其添加__metaclass__属性,定义了__metaclass__就定义了这个类的元类。

    class Foo(metaclass=something):   #py3

    __metaclass__ = something…

    元类的主要目的就是为了当创建类时能够自动地改变类。通常,你会为API做这样的事情,你希望可以创建符合当前上下文的类。假想一个很傻的例子,你决定在你的模块里所有的类的属性都应该是大写形式。有好几种方法可以办到,但其中一种就是通过设定__metaclass__。采用这种方法,这个模块中的所有类都会通过这个元类来创建,我们只需要告诉元类把所有的属性都改成大写形式就万事大吉了。__metaclass__实际上可以被任意调用,它并不需要是一个正式的类。所以,我们这里就先以一个简单的函数作为例子开始。

    图8:自定义元类

    使用class来当做元类

    由于__metaclass__必须返回一个类。

    图9:class做元类

    但是,这种方式其实不是OOP。我们直接调用了type,而且我们没有改写父类的__new__方法。现在让我们这样去处理:

    图10:oop形式的元类定义

    你可能已经注意到了有个额外的参数upperattr_metaclass,这并没有什么特别的。类方法的第一个参数总是表示当前的实例,就像在普通的类方法中的self参数一样。当然了,为了清晰起见,这里的名字我起的比较长。但是就像self一样,所有的参数都有它们的传统名称。因此,在真实的产品代码中一个元类应该是像这样的:

    图11:元类定义

    如果使用super方法的话,我们还可以使它变得更清晰一些。

    图12: 修正元类定义

    5 元类实例分析

    在阅读Django源代码时,Django自带了ORM数据库模型,这部分源码大量使用了元类,我们通过创建一个类似Django中的ORM来熟悉一下元类的使用,通常元类用来创建API是非常好的选择,使用元类的编写很复杂但使用者可以非常简洁的调用API。

    #我们想创建一个类似Django的ORM,只要定义字段就可以实现对数据库表和字段的操作。
    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()

    上面代码展示了使用ORM轻松定义数据模型,下面我们使用元类来定义类似于Django ORM类的功能。(整理的代码,可能有点问题,没试验过)

    # _*_ coding: UTF-8 _*_
    # @Time : 2020/6/11 15:50
    # @Author : Huang Ri Qiu
    # @Site : 
    # @File : type004.py
    # @Software : PyCharm
    
    #一、首先来定义Field类,它负责保存数据库表的字段名和字段类型:
    class Field(object):
        def __init__(self, name, column_type):
            self.name = name
            self.column_type = column_type
    
        def __str__(self):
            return '' % (self.__class__.__name__, self.name)
    
    class StringField(Field):
        def __init__(self, name):
            super(StringField, self).__init__(name, 'varchar(100)')
    
    class IntegerField(Field):
        def __init__(self, name):
            super(IntegerField, self).__init__(name, 'bigint')
    
    #二、定义元类,控制Model对象的创建
    
    class ModelMetaclass(type):
        '''定义元类'''
        def __new__(cls, name, bases, attrs):
            if name=='Model':
    
                return super(ModelMetaclass,cls).__new__(cls, name, bases, attrs)
    
            mappings = dict()
            for k, v in attrs.iteritems():
                # 保存类属性和列的映射关系到mappings字典
                if isinstance(v, Field):
                    print('Found mapping: %s==>%s' % (k, v))
                    mappings[k] = v
    
                for k in mappings.iterkeys():
                    #将类属性移除,使定义的类字段不污染User类属性,只在实例中可以访问这些key
                    attrs.pop(k)
                    attrs['__table__'] = name.lower() # 假设表名和为类名的小写,创建类时添加一个__table__类属性
                    attrs['__mappings__'] = mappings # 保存属性和列的映射关系,创建类时添加一个__mappings__类属性
            return super(ModelMetaclass,cls).__new__(cls, name, bases, attrs)
    
    #三、编写Model基类
    
    class Model(dict):
        __metaclass__ = ModelMetaclass
    
        def __init__(self, **kw):
            super(Model, self).__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__.iteritems():
                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))#最后,我们使用定义好的ORM接口,使用起来非常的简单。
    
    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()
  • 相关阅读:
    RNN 一对一
    js只保留整数,向上取整,四舍五入,向下取整等函数
    oracle中的decode的使用
    ORACLE里锁有以下几种模式,v$locked_object,locked_mode
    时间序列/信号处理开源数据集-转
    ORACLE常用数值函数、转换函数、字符串函数
    Oracle to_date()函数的用法
    java使double保留两位小数的多方法 java保留两位小数
    Oracle修改字段类型方法总结
    POI对Excel自定义日期格式的读取
  • 原文地址:https://www.cnblogs.com/yimai-series/p/13093959.html
Copyright © 2020-2023  润新知