python-元类和使用元类实现简单的ORM
元类
面向对象中,对象是类的实例,即对象是通过类创建出来的,在python中,一切皆对象,同样,类也是一个对象,叫做类对象,只是这个类对象拥有创建其子对象(实例对象)的能力。既然类是对象,那么类是通过什么创建出来的呢?答案就是元类。即元类就是用来创建类的“东西”。
python默认的元类:type
首先我们来看一下如何创建类的,一般我们使用class语句来创建一个类,如:
class Foo(object): pass
除了这种写法外,还可以通过type()来创建一个类,如创建上面的类可以写成:
Foo = type('Foo', (object,), {})
而实际上解释器在解析class语句创建类时,会自动将class语句转换为type()语句的写法,即type可以创建一个类,它就是元类,也是python默认的元类。
type接收三个参数:
第一个参数为创建的类名
第二个参数是一个元组,里面接收的是该类继承的父类,若不用继承父类,则为空元组
第三个参数是一个字典,里面接收的是该类的类属性和各种方法,若接收了方法,则该方法需要在type语句前先创建,创建语句和在class语句中创建方法一样
较为完整的使用type创建类的方式,如下,先创建一个Parent类(用来演示继承),使用type创建Son类,让Son类继承Parent类:
class Parent: parent_name = 'parent' # 初始化方法 def __init__(self, a): self.a = a # 实例方法 def add(self, b): return self.a + b # 类方法 @classmethod def print_class(cls): print('调用了类方法') # 静态方法 @staticmethod def print_static(): print('调用了静态方法') Son = type('Son', (Parent,), {'son_name': 'xiaoming', '__init__': __init__, 'add': add, 'print_class': print_class, 'print_static': print_static}) son = Son(100) print('-------------------实例对象son的操作----------------------') print('parent_name 属性:', son.parent_name) print('son_name 属性:', son.son_name) print('调用了实例方法add:', son.add(10)) son.print_class() son.print_static()
# 运行结果为
-------------------实例对象son1的操作----------------------
parent_name 属性: parent
son_name 属性: xiaoming
调用了实例方法add: 110
调用了类方法
调用了静态方法
上面type方式相当于如下class语句:
class Parent: parent_name = 'parent' class Son1(Parent): son_name = 'xiaoming' def __init__(self, a): self.a = a def add(self, b): return self.a + b @classmethod def print_class(cls): print('调用了类方法') @staticmethod def print_static(): print('调用了静态方法') son1 = Son1(100) print('-------------------实例对象son1的操作----------------------') print('parent_name 属性:', son1.parent_name) print('son_name 属性:', son1.son_name) print('调用了实例方法add:', son1.add(10)) son1.print_class() son1.print_static()
# 运行结果与上面运行结果一致:
-------------------实例对象son的操作----------------------
parent_name 属性: parent
son_name 属性: xiaoming
调用了实例方法add: 110
调用了类方法
调用了静态方法
自定义元类
自定义元类的主要目的就是为了当创建类时能够自动地改变类。
那么在创建类时,如果告诉解释器是通过默认的type创建还是使用我们自定义的元类来创建呢?有一个类属性__metaclass__用来标志使用的元类是什么,可以在定义类的时候,给该属性赋值,手动告诉解释器使用哪一元类。解释器在解析时,过程为查找定义类语句中有没有给__metaclass__赋值,若没有则在其父类中寻找__metaclass__属性,若还是没有则在模块层次中去寻找__metaclass__,若最终还是找不到,则使用默认的type来当做元类。在python2中使用__metaclass__写法为,定义属性__metaclass__,而在python3中的写法为:
# python2 class Foo(object): __metaclass__ = xxxx pass # python3 class Foo(object, metaclass=xxxx): pass
这里自定义一个元类UpperAttrMetaClass,假如需求为,使用自定义元类UpperAttrMetaClass,将该元类创建的所有类(如Test)的类属性名(如name)都改为大写(如NAME)
class UpperAttrMetaClass(type):
# 自定义的元类必须继承type,也可以说继承了type,那么这就是一个元类 def __new__(cls, class_name, class_parents, class_attr): """__new__方法时在__init__之前被调用的特殊方法,是python中真正的构造方法(不是__init__) 其作用是创建一个对象并返回该对象,而__init__的作用只是初始化对象的属性 在实现单例模式时可以用到这个__new__方法 这里因为我们需要通过该方法将一个传入的类对象改造一下并返回一个新的类对象,因此使用__new__方法 除了第一个固定参数cls外,其他自定义的参数分别为传入的类名,传入类的父类,传入类的属性 """ # 定义一个空字典,用来存放新的类属性 new_attr = dict() # 循环遍历原来的类属性,并将非__开头的属性的属性名改成大写,并存入新的字典 for name, value in class_attr.items(): if not name.startswith('__'): new_attr[name.upper()] = value else: new_attr[name] = value # 使用type创建一个新的类对象,该类对象的属性使用上面新字典中的属性 return type(class_name, class_parents, new_attr) class Test(object, metaclass=UpperAttrMetaClass): name = 'python' # 类Test在语句创建中,属性name为小写,但是语句创建后,属性变成了大写NAME print('Test是否有name属性', hasattr(Test, 'name')) print('Test是否有NAME属性', hasattr(Test, 'NAME')) # 运行结果为: # Test是否有name属性 False # Test是否有NAME属性 True
元类实现简单ORM
ORM是什么
ORM(Object Relational Mapping),即对象-关系映射,简称ORM。一个句话理解就是:创建一个实例对象,用创建它的类名当做数据表名,用创建它的类属性对应数据表的字段,当对这个实例对象操作时,能够执行对应MySQL语句,如:
class Goods(父类): goods_id = ('goods_id', 'int unsigined') name = ('name', 'varchar(20)') desc = ('desc', 'varchar(200)') ...... goods = Goods(goods_id=12, name='草莓', desc='这是草莓的简介') goods.save() #最终实现了sql语句 # insert into Goods(goods_id,name,desc) values (12,"草莓","这是草莓的简介")
1、所谓的ORM就是让开发者在操作数据库的时候,能够像操作对象时通过xxxx.属性=yyyy
一样简单,这是开发ORM的初衷
2、ORM的功能较为复杂,实现了一些比较复杂的SQL操作,这里主要完成一个 insert功能相类似的ORM
使用普通类实现ORM
实现思路:
1、通常ORM使用套路为,定义一个类(即对应数据库表),再类中定义类属性(即对应数据库字段),定义相应的实例方法(即对应增删改查方法),操作数据表时,只需要创建对应的类对象,然后调用对象的方法就可以完成相应的sql操作,这里我们最终实现调用save()方法,则拼出相应的insert语句,因此大概框架为
class Goods(object): goods_id = ('goods_id', 'int unsigined') name = ('name', 'varchar(20)') desc = ('desc', 'varchar(200)') def __init__(self, **kwargs): pass def save(self):
# insert into Goods(goods_id,name,desc) values (12,"草莓","这是草莓的简介") sql = 'insert into %s(%s) values (%s)' % (表名, ','.join([字段名]), ','.join([插入的值])) print(sql) goods = Goods(goods_id=12, name='草莓', desc='这是草莓的简介') goods.save()
2、那么我们就是要获取到SQL中的:表名、字段名、插入的值这三个变量:
2.1 表名即为类名,可以通过 实例对象.__class__.__name__方法获取,即在实例对象中获取对应的类名
2.2 字段名即为类属性,可以通过 实例对象.__class__.__dict__方法获取,即在实例对象中获取所有类属性
2.3 插入的值即可以通过实例属性获取
代码实现为:
class Goods(object): goods_id = ('goods_id', 'int unsigined') name = ('name', 'varchar(20)') desc = ('desc', 'varchar(200)') def __init__(self, **kwargs): # 获取表名,即对应类的名字 self.table = self.__class__.__name__ # 将传入的参数设置成实例属性 for name, value in kwargs.items(): setattr(self, name, value) def save(self): fileds = [] values = [] # 循环类属性 for name, value in self.__class__.__dict__.items(): # 如果类属性的值为元组,则认为其为定义的字段属性 if isinstance(value, tuple): # 将获取的字段名存入fileds fileds.append(value[0]) # 将字段名对应的实例属性值存入values value = getattr(self, name, None) # 如果是字符串类型则前后拼上双引号 if isinstance(value, str): value = '"' + value + '"' values.append(value) sql = 'insert into % s(% s) values (%s)' % (self.table, ','.join(fileds), ','.join([str(value) for value in values])) print(sql) goods = Goods(goods_id=12, name='草莓', desc='这是草莓的简介') goods.save() goods2 = Goods(name='香蕉', desc='这是香蕉的简介', goods_id=13) goods2.save() # 运行结果为 # insert into Goods(goods_id,name,desc) values (12,"草莓","这是草莓的简介") # insert into Goods(goods_id,name,desc) values (13,"香蕉","这是香蕉的简介")
使用元类实现ORM
上面用普通类实现ORM时,每次都要通过self.__class__获取类中值,可以通过元类(元类能够修改类属性)来解决这两个问题,但是实现思路还是不变的,只是换不同的方式去获取 表名、字段名、插入的值这三个变量。
针对循环类属性,可以在元类中,将之前定义的类属性抽离到一个新的属性中,这个属性是一个字典,字典里面存着定义的类属性
针对获取类名,可以在元类中,新建一个属性,存着类名
class ModelMetaClass(type): """ 这个元类的作用是,将原本Goods类中定义的类属性(字段)删除,并同时用一个新的属性__mapping__去存这些类属性 将这些字段属性单独抽出来放到字典里面的原因是方便遍历 """ def __new__(self, class_name, class_parents, class_attr): mappings = dict() # 将字段属性抽出放入新字典mapping中 for name, value in class_attr.items(): if isinstance(value, tuple): mappings[name] = value[0] # 删除原属性字典中的字段属性,因为已经用不到了 for k in mappings.keys(): class_attr.pop(k) # 新增mapping属性指向mapping字典 class_attr['mappings'] = mappings # 将类名作为表名 class_attr['table'] = class_name return type(class_name, class_parents, class_attr) class Goods(metaclass=ModelMetaClass): goods_id = ('goods_id', 'int unsigined') name = ('name', 'varchar(20)') desc = ('desc', 'varchar(200)') # 上面属性经过元类的__new__方法后,转化为 # mapping = { # 'goods_id': ('goods_id', 'int unsigined'), # 'name': ('name', 'varchar(20)'), # 'desc': ('desc', 'varchar(200)'), # } # table = Goods def __init__(self, **kwargs): # 将传入的参数设置成实例属性 for name, value in kwargs.items(): setattr(self, name, value) def save(self): fileds = [] # 数据库字段名 values = [] # 插入的值 for name, value in self.mappings.items(): # 将获取的字段名存入fileds fileds.append(value) # 将字段名对应的实例属性值存入values value = getattr(self, name, None) # 如果是字符串类型则前后拼上双引号 if isinstance(value, str): value = '"' + value + '"' values.append(value) sql = 'insert into %s( %s) values (%s)' % (self.table, ','.join(fileds), ','.join([str(value) for value in values])) print(sql) goods = Goods(goods_id=12, name='草莓', desc='这是草莓的简介') goods.save() goods2 = Goods(name='香蕉', desc='这是香蕉的简介', goods_id=13) goods2.save() # 运行结果为: # insert into Goods( goods_id,name,desc) values (12,"草莓","这是草莓的简介") # insert into Goods( goods_id,name,desc) values (13,"香蕉","这是香蕉的简介")
当然上述代码还可以继续改进,例如将save封装在一个基类中,每次创建不同表的类对象时,不需要重复实现save方法