美文网首页
python学习笔记-tip44(type()和metaclas

python学习笔记-tip44(type()和metaclas

作者: 黑键手记 | 来源:发表于2018-09-27 15:23 被阅读21次

先说type()

动态语言和静态语言最大的不同,是 函数和类的定义不是编译时定义的,而是运行时动态创建的

下面我们创建一个类

如果我们按照普通的写法写的话


我们看到
Hello类的类型是 type
而h的类型是Hello类
上面我们说到类(class)的定义是在运行时动态创建的,那么这个Hello类其实就是动态创建的,而type()方法,其实就是动态创建时调用的方法

所以现在,我们知道了type()的作用了吧:
  • 返回一个对象的类型
  • 还可以创建出新的类,比如我们可以通过type()创建出上面定义的Hello类

我们来定义一下:


先对使用type()去创建类的实例方法做一个说明

    Hello=type('Hello',(object,),dict(hello=func))
  • 参数1:表示class的名字
  • 参数2:表示继承父类的集合,tuple类型(所以只有一个父类时,后边添加了“,”逗号)
  • 参数3:class 方法名称与现有函数绑定,这里表示指定hello函数,并且绑定了func
    这么一说是不是就明白了呢。哈哈

metaclass-元类

除了上面的使用type()动态创建类之外,还可以使用metaclass
其实metaclass的作用就是:

定义类,创建类,有了类之后再创建对象 这整个过程中的第一步(所以特地用红色字来表示)

我们来先看例子,需要注意的是,当我们在定义metaclass时,最好是以Metaclass为后缀,以便于区分

  • 定义metaclass类

     class MyListMetaclass(type):
            def __new__(cls,name,bases,attrs):
                  attrs['add']=lambda self,value:self.append(value)
                  return type.__new__(cls,name,bases,attrs)
    
  • 有了metaclass之后,我们去创建一个类

      #在创建类时,需要传入关键字参数metaclass
      class MyListClass(metaclass=MyListMetaclass):
                pass
    

这样,python解释器在创建MyClass时,就会通过MyMetaclass.__new__()来进行创建
所以我们可以在MyMetaclass.__new__()方法中添加新的方法,然后返回修改之后的定义

new方法接收到的参数依次是:

  • cls--当前准备创建的类的对象
  • name--类的名字
  • bases--类继承的父类集合
  • attrs--类的方法的集合

我们上方的示例演示的是创建一个 list列表一样的类,这个类拥有add方法,其实就是封装了append方法
我们来看下完整示例


这个时候你会想,动态修改有什么意义?直接在MyList定义中写上add()方法不是更简单吗?正常情况下,确实应该直接写,通过metaclass修改纯属变态。
对,对于简单的逻辑,如果你还使用metaclass那就是

变态


但是如果对于复杂的逻辑,比如ORM,那么用metaclass能达到

事半功倍

的效果

使用metaclass 完成ORM框架

ORM简介

ORM全称:“Object Relational Mapping”即对象-关系-映射
就是把关系数据库的一行映射为一个对象
也就是一个对象对应于一个表
这样一来,写代码更加简单,不用直接操作sql语句

要编写一个ORM框架,所有的类都只能动态定义,因为只有使用者才能根据表的结构定义出对应的类来

下面,我们尝试编写一个ORM框架

####第一步、编写底层框架的第一步:
#### 是将调用逻辑写出来,比如我们要定义ORM,那么先定义调用逻辑
#### 这里我们定义User类来操作对应的数据库表User,我们想这样使用

      class User(Model):
                #定义类的属性到数据库列中的映射
                id= IntegerField('id')
                name=StringField('username')
                email=StringField('email')
                password=StringField('password')
      #创建一个实例
      u=User(id=12,name='xiaoming',email='guaju@126.com',password='123456')
      #创建实例,意味着做了数据库的新增记录的操作,下一步直接保存即可
      u.save()

好,我们现在讲操作逻辑写出来了,现在我们分析一下具体的操作逻辑中使用到的类及方法

  • Model 父类
  • IntegerField、StringField 整数 字符类型属性
    这两类是由ORM框架来提供,剩下的save()方法则由metaclass来完成,因为save()操作就等同于动态处理了

好,我们接着来实现

第二步、定义Field类,他负责保存数据库表的字段名和字段类型

    class Field(object):
            def __init__(self,name,column_type):
                  self.name=name
                  self.column_type=column_type
            def __str__(self):
                  return <%s:%s> %(__class__.__name__,self.name)

在Field基础上,定义一些我们需要的子类,这里,我们就定义IntegerField和StringField两个就够了

    class IntegerField(Field):
            def __init__(self,name):
                  super(IntegerField,self).__init__(name,'bigint')

    class StringField(Field):
            def __init__(self,name):
                  super(StringField,self).__init__(name,'varchar(100)')

第三步、编写最复杂的的ModelMetaclass类了

      class  ModelMetaclass(type):
                def __new__(cls,name,bases,attrs):
                      if name=='Model':
                          return type.(cls,name,bases,attrs)
                      print('Found Model:%s' %name)
                      mappings=dict()
                      for k,v in attrs.items():
                            if isintance(v,Field):
                               print('Found Mapping:%s-->%s' %(k,v))
                               mappings[k]=v
                      for k in mappings.keys():
                            attrs.pop(k)
                      #保存属性和列的对应关系
                      attrs['__mappings__']=mappings
                      #假设表名和类名一致
                      attrs['__name__']=name
                      return type.__new__(cls,name,bases,attrs)

基类Model

      class Model(dice,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__.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)) 

欧了,代码部分完事了
现在说一下逻辑

  • 当用户创建一个User类时,Python解释器会首先在当前类User查找metaclass,如果没有找到,就继续在父类Model中查找metaclass
  • 找到了,就使用该metaclass去创建User类
  • 在Modelmetaclass中,会做这些事
    • 排除掉对Model类的修改
    • 查找当前类User定义的所有属性,找到一个就放到mappings这个dict中去,同时从类属性中删除该Field属性,否则容易造成运行时错误实例的属性会遮盖类的同名属性
    • 把表名保存到table中,这里简化,表名为类名

然后在Model中就可以定义各种操作数据库的方法,比如save(),delete(),find(),update()等

我们实现了save方法,即把一个实例保存到数据库中,因为有表名以及属性到字段的映射和属性值的集合,就可以构造出Insert语句

这样,这么点代码就实现了ORM框架的搭建,是不是很简单

。。。。。。
反正我写完这个文章后,我的内心是崩溃的。。。不过看不懂没关系,看懂了最好,因为元类这个东西,暂时对你们没什么帮助哦,当你们用到了这个东西,再回过头来看也不迟。

相关文章

网友评论

      本文标题:python学习笔记-tip44(type()和metaclas

      本文链接:https://www.haomeiwen.com/subject/kbnsoftx.html