zoukankan      html  css  js  c++  java
  • python中的metaclass

    首先看下面的代码:

    # coding: utf-8
    
    class Test(object):
        pass
    
    
    print Test.__class__  # type
    print Test.__base__   # object
    
    t = Test()
    print t.__class__     # Test
    print t.__class__.__class__  # type
    
    print Test.__class__.__class__.__class__  # type
    
    a = type('Foo', (), {})
    print a  # class Foo
    
    t = a()
    print type(t)  # Foo
    print t.__class__  # Foo
    print t.__class__.__class__  # type
    
    

    从代码中可以看出:

    1. t的__class__,为Test,这是它所属的类
    2. Test.class 为type,也就是说,Test这个类也是个对象,他是type这个类的对象。

    在Python中,一切都是对象,所以类本身也是对象,类对象所属的类是type类。

    那么,Python中的类都是type类的对象,而type类实现了__call__调用,实现__call__的类的对象都可以当做函数一样调用,所以所有的type对象都是函数,也就是说,所有的类都是函数。

    以前一直以为type是个函数,但实际上,type本身是个类。

    所以,Python中的类型体系可以看成:

    type -> Test -> t
    

    而Python中对象的类型可以这样追溯:

    t -> Test -> type -> type -> ...
    

    metaclass

    我们使用类来创建对象,而Python中可以通过元类来动态创建类。
    metaclass可以是一切可以执行的东西,也就是说,可以是函数,也可以是类。

    # coding: utf-8
    
    def upper_attrs(class_name, class_parents, class_attrs):
    
        attrs = {}
        for key in class_attrs:
            if not key.startswith('__'):
                attrs[key.upper()] = class_attrs[key]
    
        return type(class_name, class_parents, attrs)
    
    class Foo:
        __metaclass__ = upper_attrs
        bar = 'haha'
    
    if __name__ == '__main__':
        print hasattr(Foo, 'bar')
        print hasattr(Foo, 'BAR')
    
        print type(Foo)
    
    
    

    这里我们将upper_attrs当做一个元类,它的作用是将类的属性改为大写。所以Foo中的bar属性改为BAR属性。

    metaclass的作用主要是:

    1. 拦截类的创建
    2. 修改类
    3. 返回被修改的类

    使用类实现metaclass

    
    # coding: utf-8
    
    
    class UpperAttrsMeta(object):
    
        def __new__(metaclass, class_name, class_parents, class_attrs):
            print metaclass
            attrs = {}
            for key in class_attrs:
                if not key.startswith('__'):
                    attrs[key.upper()] = class_attrs[key]
    
            return type(class_name, class_parents, attrs)
    
    class Test:
        __metaclass__ = UpperAttrsMeta
    
        foo = 'bar'
    
    if __name__ == '__main__':
        print hasattr(Test, 'foo')
        print hasattr(Test, 'FOO')
        t = Test
        print t.FOO
        print type(t)
        print type(Test)
        print type(UpperAttrsMeta)
    
    
    

    使用metaclass实现一个简易的ORM

    假设我们想实现一个ORM,这样去使用:

    class User(Model):
        id = IntegerField('id')
        name = StringField('username')
        email = StringField('email')
        password = StringField('password')
    
    u = User(id=123455, name='haha', email='haha@301.com', password='hhhhh')
    u.save()
    
    

    我们先实现各种Field:

    # coding: utf-8
    
    class Field(object):
        def __init__(self, name, column_type):
            self.name = name  # 列名
            self.column_type = column_type  # 列的类型
    
        def __str__(self):
            # 实质是打印table名称和列名
            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')
    
    

    Field的主要作用是记录列名和类型之间的关系。就好比 name 和 varchar
    上面实现了Field,代表数据类型。下面我们需要实现数据库的table,每种类型我们称为一种model。

    下面我们使用metaclass实现model。

    class ModelMetaClass(type):
        def __new__(cls, name, bases, attrs):
            if name == 'Model':
                return type.__new__(cls, name, bases, attrs)
            mappings = dict()
            for k, v in attrs.iteritems():
                if isinstance(v, Field):
                    # 记录name -> StringField
                    print 'Found mapping: %s->%s' % (k, v)
                    mappings[k] = v
    
            for k in mappings.iterkeys():
                # 防止重复 a.name
                attrs.pop(k)
    
            attrs['__table__'] = name  # 表名称
            attrs['__mappings__'] = mappings  # 属性和列名的映射关系
            return type.__new__(cls, name, bases, attrs)
    

    上面提过,metaclass的主要作用是拦截类的生成,并进行修改。

    这里的ModelMetaClass,将类的属性中定义的 列名和数据类型单独摘取出来,并将它们保存在__mappings__中。
    另外,提取出列名和类型后,需要将这些属性去掉。

    以User为例,提取完id,name,email和password后,需要将它们删除,因为这四个属性为类属性,根据User定义的对象u也具有这四个属性,此时u.name为StringField对象,这样显然不合理,所以需要将这些属性去掉。

    这些属性的唯一价值也是提供给metaclass使用。

    下面实现Model:

    class Model(dict):
        __metaclass__ = ModelMetaClass
    
        def __init__(self, **kwargs):
            super(Model, self).__init__(**kwargs)
    
        def __getattr__(self, key):
            try:
                return self[key]
            except KeyError:
                raise AttributeError("'%s' object has not Attribute: %s" % (self.__class__.__name__, key))
    
        def __setattr__(self, key, value):
            self[key] = value
    
        def save(self):
            fields = []
            params = []
            args = []
            # 注意k 和v.name可能不同 数据库中以v为主
            for k, v in self.__mappings__.iteritems():
                fields.append(v.name)  # 列名
                params.append('?')
                args.append(getattr(self, k, None))
            _fields = ','.join(fields)
            _params = ','.join(params)
            sql = 'insert into %s (%s) value (%s)' % (
                self.__table__, _fields, _params)
            print 'SQL: %s' % sql
            print str(args)
    

    save函数的过程就是根据__mappings__中记录的列名,从dict中提取出值。将其保存在args中。fields中保存的是列名。

    运行结果如下:

    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) value (?,?,?,?)
    ['hhhhh', 'haha@301.com', 'haha', 123455]
    

    参考资料:

    http://blog.jobbole.com/21351/
    http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001386820064557c69858840b4c48d2b8411bc2ea9099ba000

  • 相关阅读:
    给VS2010自动设置模板,加头注释
    使用委托解决"线程间操作无效: 从不是创建控件“textBox1”的线程访问它" 问题
    正则表达式
    用rz,sz命令在xshell传输文件
    较快的centos源
    《高性能Linux服务器构建实战》一书纠错汇总(2月14日更新)
    cat和rev
    配置centos6.0为Router
    同一个进程中生成的不同线程的栈是互相可见的
    C++用new来创建对象和非new来创建对象的区别
  • 原文地址:https://www.cnblogs.com/inevermore/p/4620048.html
Copyright © 2011-2022 走看看