• 使用元类 编写ORM


    元类

    一句话: 元类定制类的创建行为

    知识点

    1.类的创建: python这种动态语言,函数和类的定义,不是编译时定义的,而是运行时动态创建的。

    Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。

    2.控制类的创建行为,除了手动调用type()外,还可以使用metaclass。

    3.生成类实例的执行顺序: 扫描类的定义准备生成对象->等会,先去生成父类->还不行,先去父类的元类那里看看有什么指示

    -->执行元类的__new__()-->执行父类的__init__-->子类创建

    元类基本功能 定制类

    类似于父类继承,比如给子类添加一个方法属性:

    '''

    metaclass所以必须从type类型派生:通常写成以Metaclass结尾

    class ListMetaclass(type):
    def new(cls, name, bases, attrs):
    attrs['add'] = lambda self, value: self.append(value)
    return type.new(cls, name, bases, attrs)

    Python解释器在创建MyList时,要通过ListMetaclass.__new__()来创建

    class MyList(list, metaclass=ListMetaclass):
    pass
    '''

    调用一下:

    '''
    L = MyList()
    L.add(1)
    '''

    从上面代码中 注意到关键词type,要知道,python调用type()创建类的,如果我们在元类的定义里什么都不做,

    也就是直接调用了type.new(cls, name, bases, attrs),岂不说明类的创建本来就是以type为元类的

    所谓的元类不就是在准备type(MyList)时,临时插入一段代码?

    元类定制类的创建行为

    父类继承方式可以定制子类,但是你不能根据还未出生的子类动态调整

    同一的元类的定制是可以被继承下去的

    比如写ORM框架,一个类对应一个数据库的表,我们首先会想到将数据库的操作封装到类里面,但是我现在不是使用者,

    不确定数据库里表的定义,那么相应类里面的属性也就不确定,下面这个类(表)只是形式是这样,类(表)名,字段(类的属性)

    都是可以变化的,如果要写框架,也就是写父类Model时,怎么才能获取子类User(还不一定叫这名)的属性等信息呢?

    '''

    编写底层模块的第一步,就是先把调用接口写出来。

    class User(Model):
    id = IntegerField('id')
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')

    创建一个实例:

    u = User(id=12345, name='xcl', email='test@orm.org', password='pwd')

    保存到数据库:

    u.save()
    '''

    怎么获取/操作类的属性呢 <元类的__new__() 方法

    在元类中,我们可以获取当前准备创建的类的基本信息,

    new(cls, name, bases, attrs)参数分别是指: 当前准备创建的类的对象,准备创建的类名,父类集合,类的属性集合

    注意__new__还在父类Model创建前,__new__里的attrs都是指类属性而不是实例属性. 相应的,User类中添加的都是类成员而非实例成员

    流程如下:

    graph BT; 创建一个User对象u-->先创建父类Model; 先创建父类Model-->调用父类Model的init; 调用父类Model的init-->Model的元类的new; 获取并操作子类User的属性和类名-->Model的元类的new; 调用父类的统一接口save-->获取并操作子类User的属性和类名; 属性和类名各不相同的子类-->调用父类的统一接口save;

    虽然metaclass的编写会比较复杂,但ORM的使用者用起来简单。

    -- 真正的代码在此 --

    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)  #eg: <IntegerField:id>
    
    class IntegerField(Field):
    
        def __init__(self, name):
            super().__init__(name, 'int')
    
    
    class StringField(Field):
    
        def __init__(self, name):
            super().__init__(name, 'varchar(50)')
    
    
    #attrs 是 类的属性集合  
    #__mappings__  __table__是自己添加的两个属性,他两在使用该元类创建的父类 Model中也能获取到
    class ModelMetaClass(type):
        def __new__(cls, name, bases, attrs):
            if name == "Model":  #不是实际使用的类,跳过
                return type.__new__(cls, name, bases, attrs)
            mapping = dict()
            for k, v in attrs.items():
                if isinstance(v, Field):
                    mapping[k] = v
            for k in mapping.keys():
                attrs.pop(k)  #从类属性中删除该Field属性 否则,容易造成运行时错误(实例的属性会遮盖类的同名属性)
                #实例 User(id=1234,..) 但是User类中也有id等属性
            attrs['__mappings__'] = mapping  ## 保存属性和列的映射关系
            attrs['__table__'] = name  #添加属性__table__存放准备创建的类的名字, 也就是表的名字
            # 这样一来,父类Model才能获取它未出生的儿子的类名
    
            return type.__new__(cls, name, bases, attrs)
    
    
    class Model(dict,metaclass=ModelMetaClass):  #指示使用ListMetaclass来定制类,传入关键字参数metaclass
    
        def __init__(self, **kw):
            return super().__init__(**kw)
    
        def __getattr__(self, key): #m.key==>m[key]
            try:
                return self[key]
            except KeyError as e:
                raise AttributeError(r"'Model' object has no attribute %s" % key)
    
        def __setattr__(self, name, value):#m.key=value ==> m[key]=value
            self[name] = value
    
        def save(self):
            fields = []
            params = []
            args = []
            for k, v in self.__mappings__.items():
                fields.append(v.name) #v是一个Field对象
                params.append('?') #sql的占位符 ?
                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))
    
    ###到使用部分就简单了
    class User(Model):
        id = IntegerField('id')
        name = StringField('username')
        email = StringField('email')
        password = StringField('password')
    
    # 下面实例化时的 id name等会和 上面的类的成员变量同名冲突, 幸好在元类里面attrs.pop()掉了
    u = User(id=1234, name='xcl', email='chunlaixiao@163.com', password='123456')
    u2 = User(id=788, name='wang', email='wang@163.com', password='123456')
    
    u.save()
    u2.save()
    print(u.name) ###这是调用__getattr__()方法 返回u[name]
    
  • 相关阅读:
    自制 移动端 纯原生 Slider滑动插件
    CSS3动画几个平时没注意的属性
    移动开发屏幕适配分析
    CSS3伸缩盒Flexible Box
    grep命令做永久别名 显示颜色
    centos 正则,grep,egrep,流式编辑器 sed,awk -F 多个分隔符 通配符 特殊符号. * + ? 总结 问加星 cat -n nl 输出文件内容并加上行号 alias放~/.bash_profile 2015-4-10 第十三节课
    centos shell基础 alias 变量单引号 双引号 history 错误重定向 2>&1 jobs 环境变量 .bash_history source配置文件 nohup & 后台运行 cut,sort,wc ,uniq ,tee ,tr ,split, paste cat> 2.txt <<EOF 通配符 glob模式 发邮件命令mail 2015-4-8 第十二节课
    wget 命令大全
    centos 阶段复习 2015-4-6 dd命令 hosts.allow和hosts.deny 啊铭的myssh脚本 清空history命令历史 /dev/zero 零发生器 /dev/null 黑洞 /dev/random 生成随机数 第十一节课
    Linux下LDAP统一认证解决方案
  • 原文地址:https://www.cnblogs.com/ShawSpring/p/10634292.html
Copyright © 2020-2023  润新知