面向对象高级编程-元类
动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。
如自定义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个参数:
- class的名称
- 继承的父类集合,使用
tuple
传入,只有一个父类时,需要加,
:(object,)
- 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__()
方法接收到的参数依次是:
- 当前准备创建的类的对象
- 类的名字
- 类继承的父类集合
- 类的方法集合
使用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
和属性类型StringField
、IntegerField
是由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']
逻辑分析
- 当用户定义一个
class User(Model)
时,metaclass
可以隐式地继承到子类,会使用父类Model
中定义的metaclass=ModelMetaclass
来创建User类。 - 调用
ModelMetaclass
,元类的具体工作:- 对类名为
Model
的类不做修改修改 - 遍历定义类的所有属性,将继承
Field
类的属性保存到__mappings__
属性中,同时从类属性中删除该Field
属性,否则,容易造成参数读取错误 - 把表名保存到
__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类中的id
、name
等属性,此时属性对应的值为<IntegerField: id>
等
- 如果做了删除处理,在save()方法执行
args.append(getattr(self, k, None))
时的流程如下:getattr(self, k, None)
获取id
、name
等属性时,User类中不存在该属性,会调用父类Model
类的__getattr__()
方法,进而返回self[k]
,即字典中存储的参数值