美文网首页
一周一个Python语法糖:(三) 元类

一周一个Python语法糖:(三) 元类

作者: 寒夏凉秋 | 来源:发表于2017-04-22 17:12 被阅读0次

    先来了解下python魔法的内核吧:

    一切皆对象

    • 一切皆对象
    • 一切皆有类型
    • “class” and "type" 之间本质上上并无不同
    • 类也是对象
    • 他们的类型都是type
    class ObjectCreator(object):
            pass
    

    这段代码将在内存中创建一个对象,名字叫ObjectCreator.

    这个对象(类)自身拥有创建对象的(类实例的能力),##

    而这也就是它为什么是一个类的原因##

    你可以对该对象进行以下操作:

    1. 将它赋值给一个变量
    2. 可以拷贝它
    3. 可以为它增加属性
    4. 可以将其作为函数参数进行传递
    #交互型
    >>> def echo(o):
    ...     print(o)
    ... 
    >>> echo(my_object)
    <__main__.ObjectCreator object at 0x7fcf7d0d6c50>  #你可以将类做为参数传给函数
    >>> ObjectCreator.new_attrituer=echo    #  你可以为类增加属性
    >>> print(hasattr(ObjectCreator,'new_attrituer'))
    True
    >>> print(ObjectCreator.new_attrituer) #新加的类的方法属性
    <function echo at 0x7fcf7d171f28>
    

    一:用type创建一个类

    type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))
    #三个参数的类型分别是str,tuple,dic
    

    比如:

    #目标类:
    class Animal(object):
        def __init__(self,name):
            self.name=name
        def eat(self):
            pass
        def go_to_eat(self):
            pass
    
    #用type创建
    def init(self,name):
        self.name=name
    def eat(self):
        pass
    def go_to_eat(self):
        pass
    Animal=type('Animal',(object,),{
        '__init__':init,
        'eat':eat,
        'go_to_eat':go_to_eat
    })
    
    
    >>>print(Animal)
    <class '__main__.Animal'>
    

    这样我们实现了用type去动态创建一个类的
    BUT~这样是不是太麻烦了!!!(抬走下一位!)

    二:metaclass登场

    metaclass,直译为元类,简单的解释就是:

    当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。

    但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。

    连接起来就是:先定义metaclass,就可以创建类,最后创建实例。

    所以,metaclass允许你创建类或者修改类。换句话说,你可以把类看成是metaclass创建出来的“实例”。

    # metaclass是创建类,所以必须从`type`类型派生:
    class Listmetaclass(type):
        def __new__(cls,name,bases,attrs):
            attrs['add']=lambda self,value:self.append(value)
            return type.__new__(cls,name,bases,attrs)
        
    class Mylist(list):
        __metaclass__=Listmetaclass
    
    
    • 按照默认习惯.metaclass的类名总是以Metaclass结尾,以便清楚地表示这是一个metaclass
    • 当我们写下__metaclass__=Listmetaclass时候,它表示解释器在创建Mylist的时候
      ,要通过Listmetaclass的__new__()方法创建
      在此,我们可以修改类的定义,比如:加上新的方法,然后返回修改后的定义.
    • __new__()方法接受的参数依次是:
      1. 当前准备创建的类的对象
      2. 类的名字
      3. 类继承的父类的集合
      4. 类的方法的集合

    元类的主要目的就是为了当创建类时能够自动改变类.

    使用到元类的代码比较复杂,这背后的原因倒并不是因为元类本身,

    而是因为你通常会使用元类去做一些晦涩的事情,

    依赖于自省,控制继承等等。

    确实,用元类来搞些“黑暗魔法”是特别有用的,

    因而会搞出些复杂的东西来。

    就这个例子来说:

    • 拦截类的创建(拦截Mylist的创建)
    • 修改类(给Mylist增加add的方法)
    • 返回修改之后的类

    **“元类就是深度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。” **-----Tim Peters

    元类的主要作用的创建API,一个典型的例子是 ORM.

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

    ~

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

    我们来尝试写一个简单的ORM框架吧
    首先我们要知道使用者会调用什么接口:
    比如,使用者可能会定义一个User类来操作数据表User.

    class User(Model):
      #创建User表
      #创建四列属性
        id=IntegerField('id')
        name=StringField('username')
        email=StringField('email')
        password=StringField('password')
        
    # 创建一个实例:
    u=User(id='12345',name='zhou',email='124@qq.com',password='123455')
    #保存到数据库
    u.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>' %(self.__class__.__name__,self.name)
    

    在Field的基础上,我们来定义各种Field:

    #为了简化,我们先定义好各种属性的type
    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')
    

    接下来,我们要编写元类了

    class ModelMetaclass(type):
        def __new__(cls,name,bases,attrs):
            #如果是基类,直接返回(即不对model类进行修改)
            if  name=='Model':
                return type.__new__(cls,name,bases,attrs)
            print('Found model: %s' %name)
            #如果是其他类,则进行装饰
            #取出所有类属性,将其放入mapping
            mappings=dict()
            for key,value in attrs.iteritems():
                if isinstance(value,Field):
                    print('Found mapping: %s==>%s' %(key,value))
                    mappings[key]=value
            for key in mappings.iterkeys():
                attrs.pop(key)
            attrs['__mappings__']=mappings
            attrs['__table__']=name
            return type.__new__(cls,name,bases,attrs)
            
    

    编写基类:

    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 key,value in self.__mappings__.iteritems():
                fields.append(value.name)
                params.append('?')
                args.append(getattr(self,key,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)时候,
    解释器首先在当前类User的定义中寻找
    metaclass,如果没找到,就在继承的父类中继续找.,
    找到了,就用model中的定义的ModelMetaclass去创建User类
    也就是说,metaclass可以隐式地继承到子类,但子类自己却感觉不到。

    在ModelMetaclass中做的事情:

    • 排除对Model 的修改
    • 在当前类 中查找定义的类的属性,如果找到一个FIeld属性,就保存到__mappings__字典中
      同时从类属性中删除Field属性,否则容易造成运行时的错误
    • 把表名保存到__table__中

    在Model 类中,就可以定义各种操作数据库的方法了(列属性保存在__mappings__)中

    全部代码:

    #!/usr/bin/python3
    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(StringField,self).__init__(name,'varchar(100)')
    class IntegerField(Field):
        def __init__(self,name):
            super(IntegerField,self).__init__(name,'bigint')
    class ModelMetaclass(type):
        def __new__(cls,name,bases,attrs):
            if  name=='Model':
                return type.__new__(cls,name,bases,attrs)
            print('Found model: %s' %name)
            mappings=dict()
            for key,value in attrs.iteritems():
                if isinstance(value,Field):
                    print('Found mapping: %s==>%s' %(key,value))
                    mappings[key]=value
            for key in mappings.iterkeys():
                attrs.pop(key)
            attrs['__mappings__']=mappings
            attrs['__table__']=name
            return type.__new__(cls,name,bases,attrs)
            
    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 key,value in self.__mappings__.iteritems():
                fields.append(value.name)
                params.append('?')
                args.append(getattr(self,key,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')
        
    u=User(id='12345',name='zhou',email='124@qq.com',password='123455')
    u.save()  
           
    

    运行结果:

    Found model: User
    Found mapping: email==><StringField:email>
    Found mapping: password==><StringField:password>
    Found mapping: id==><IntegerField:id>
    Found mapping: name==><StringField:username>
    SQL:insert into User(password,email,username,id) values(?,?,?,?)
    ARGS:['123455', '124@qq.com', 'zhou', '12345']
    
    

    最后说几句:

    • 元类(Metaclass)类似于装饰器,可以在类的创建时动态修改类.

    • 元类语法糖:__metaclass__

    学习参考:
    廖雪峰Python教程
    深入理解元类
    5分钟理解元类

    相关文章

      网友评论

          本文标题:一周一个Python语法糖:(三) 元类

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