Python05

面向对象高级编程-元类

动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。
如自定义hello模块,创建hello.py文件:

class Hello(object):
    def hello(self, name='world'):
        print('Hello, %s.' % name)

当Python解释器载入hello模块时,就会依次执行该模块的所有语句,执行结果就是动态创建出一个Hello的class对象,测试如下:

from hello import Hello
h = Hello()
h.hello()    # Hello, world.
type(Hello)  # <class 'type'>
type(h)      # <class 'hello.Hello'>

type()

type()函数可以查看一个类型或变量的类型:

  • Hello是一个class,类型为type
  • h是一个实例,类型为class Hello

class的定义是运行时动态创建的,而创建class的方法就是使用type()函数。

type()函数的功能:

  • 返回一个对象的类型
  • 创建出新的类型,可以通过type()函数创建类,而无需通过class Hello(object)...定义
def fn(self, name='world'): # 先定义函数
    print('Hello, %s.' % name)

Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
h = Hello()
h.hello()       # Hello, world.
type(Hello)     # <class 'type'>
print(type(h))  # <class '__main__.Hello'>

使用type()函数创建class对象,传入3个参数:

  1. class的名称
  2. 继承的父类集合,使用tuple传入,只有一个父类时,需要加,(object,)
  3. class的方法名,使用dict传入方法名=绑定函数dict(hello=fn)

metaclass

除了使用type()函数创建类外,还可以使用metaclass控制类的创建行为,metaclass可以创建类或者修改类。

class可以看作实例的模板,而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)

使用metaclass创建类时,Python解释器会调用metaclass__new__()方法来创建class。
所以,可以通过修改metaclass__new__()方法来修改类的定义,如:添加新方法,然后返回修改后的定义。

__new__()方法接收到的参数依次是:

  1. 当前准备创建的类的对象
  2. 类的名字
  3. 类继承的父类集合
  4. 类的方法集合

使用metaclass

创建class:

class MyList(list, metaclass=ListMetaclass):
    pass

通过在创建类时传入关键字参数metaclass,使元类生效。

使用元类编写ORM框架

metaclass可以动态的修改类的定义。可以用于ORM(Object Relational Mapping),对象-关系映射。ORM会将关系数据库的一行映射为一个实例对象,行所在的表映射为实例对应的类。通过ORM可以避免直接操作SQL语句。

编写一个ORM框架,所有的类都只能动态定义,因为类对应的表只有在创建表时才能得到其结构。

设计调用接口

设计使用者的使用方式,如:使用者定义一个User类来操作对应的数据库表User

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()

父类Model和属性类型StringFieldIntegerField是由ORM框架提供的,剩下的方法比如save()全部由父类Model自动完成。
虽然metaclass的编写会比较复杂,但ORM的使用者用起来却异常简单。

实现工具类

Field类

class Field():
    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派生子类

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')

实现metacalss

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)

        # 非Model类调用,进行映射初始化
        mappings = dict()
        for k, v in attrs.items():
            if isinstance(v, Field):
                print('Found mapping: %s -> %s' % (k, v))
                mappings[k] = v
        # 从类属性中删除Field属性,否则,实例的属性可能会遮盖类的同名属性
        for k in mappings.keys():
            attrs.pop(k)
        attrs['__mappings__'] = mappings  # 保存属性和列的映射关系
        attrs['__table__'] = name        # 表名与类名一致

        return type.__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 AttrbuteError("'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 = f"insert into {self.__table__} {','.join(fields)} values {','.join(params)}"

        print('SQL: %s' % sql)
        print('ARGS: %s' % str(args))

使用ORM框架

创建自定义类

通过继承Model类来使用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')
# Found model: User
# Found mapping: id -> <IntegerField: id>
# Found mapping: name -> <StringField: username>
# Found mapping: email -> <StringField: email>
# Found mapping: password -> <StringField: password>


u.save()
# SQL: insert into User (id,name,email,password) values (?,?,?,?)
# ARGS: [12345, 'Michael', 'test@orm.org', 'my-pwd']

逻辑分析

  1. 当用户定义一个class User(Model)时,metaclass可以隐式地继承到子类,会使用父类Model中定义的metaclass=ModelMetaclass来创建User类。
  2. 调用ModelMetaclass,元类的具体工作:
    1. 对类名为Model的类不做修改修改
    2. 遍历定义类的所有属性,将继承Field类的属性保存到__mappings__属性中,同时从类属性中删除该Field属性,否则,容易造成参数读取错误
    3. 把表名保存到__table__属性中,表名默认为类名。

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

关于删除类的属性问题

为什么在ModelMetaClass类中要删除Field属性:

for k in mappings.keys():
    attrs.pop(k)

因为User类中定义了字段属性:

id = IntegerField('id')
name = StringField('username')
email = StringField('email')
password = StringField('password')

删除与不删除的执行过程会相同:

  • 如果不做删除处理,在save()方法执行args.append(getattr(self, k, None))时的流程如下:
    • getattr(self, k, None)会直接获取到User类中的idname等属性,此时属性对应的值为<IntegerField: id>
  • 如果做了删除处理,在save()方法执行args.append(getattr(self, k, None))时的流程如下:
    • getattr(self, k, None)获取idname等属性时,User类中不存在该属性,会调用父类Model类的__getattr__()方法,进而返回self[k],即字典中存储的参数值
tag(s): Python ORM 
show comments · back · home