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方法
python-元类和使用元类实现简单的ORM的更多相关文章
- Python学习_13_继承和元类
继承 继承的含义就是子类继承父类的命名空间,子类中可以调用父类的属性和方法,由于命名空间的查找方式,当子类中定义和父类同名属性或者方法时,子类的实例调用的是子类中的属性,而不是父类,这就形成了pyth ...
- 通过 python的 __call__ 函数与元类 实现单例模式
简单一句话,当一个类实现__call__方法时,这个类的实例就会变成可调用对象. 直接上测试代码 class ClassA: def __call__(self, *args, **kwargs): ...
- 【python进阶】详解元类及其应用1
前言 元类在python中是很重要的一部分,我将分两次去讲解元类及其应用,此篇为详解元类及其应用第一篇,下面开始今天的说明~~~ 1. 类也是对象 在⼤多数编程语⾔中,类就是⼀组⽤来描述如何⽣成⼀个对 ...
- 【python进阶】详解元类及其应用2
前言 在上一篇文章[python进阶]详解元类及其应用1中,我们提到了关于元类的一些前置知识,介绍了类对象,动态创建类,使用type创建类,这一节我们将继续接着上文来讲~~~ 5.使⽤type创建带有 ...
- python中的单例模式、元类
单例模式 单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在.当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场. ...
- [转]深刻理解Python中的元类(metaclass)以及元类实现单例模式
使用元类 深刻理解Python中的元类(metaclass)以及元类实现单例模式 在看一些框架源代码的过程中碰到很多元类的实例,看起来很吃力很晦涩:在看python cookbook中关于元类创建单例 ...
- python面试题~反射,元类,单例
1 什么是反射?以及应用场景? test.py def f1(): print('f1') def f2(): print('f2') def f3(): print('f3') def f4(): ...
- 深刻理解Python中的元类(metaclass)以及元类实现单例模式
在理解元类之前,你需要先掌握Python中的类.Python中类的概念借鉴于Smalltalk,这显得有些奇特.在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段.在Python中这一点仍 ...
- python(七):元类与抽象基类
一.实例创建 在创建实例时,调用__new__方法和__init__方法,这两个方法在没有定义时,是自动调用了object来实现的.python3默认创建的类是继承了object. class A(o ...
- Python元类(metaclass)以及元类实现单例模式
这里将一篇写的非常好的文章基本照搬过来吧,这是一篇在Stack overflow上很热的帖子,我看http://blog.jobbole.com/21351/这篇博客对其进行了翻译. 一.理解类也是对 ...
随机推荐
- shell编程之系统环境变量2
本课程是<Tony老师聊shell——变量>课程的延续,主要介绍Linux shell编程基础中的运算符.包括declare命令.数值运算方法和变量测试.首先在declare命令中介绍了数 ...
- SSH网上商城四
第29课:10-SSH网上商城:购物模块的实体的封装 1.现在我们要实现购物车的模块,当用户在点击 加入购物车按钮的时候需要跳转到 上面我们需要对购物车的对象进行封装 上面一个商品就对应一个记录项,购 ...
- redis高级命令2
主服务负责数据的写,从服务器负责客户端的高并发来读 创建主从复制 clone不能让上面的mac地址不能重复,IP地址也不能重复 122和123是从服务器,我们修改二者的配置文件 其中 192.168. ...
- Java笔试面试总结—try、catch、finally语句中有return 的各类情况
前言 之前在刷笔试题和面试的时候经常会遇到或者被问到 try-catch-finally 语法块的执行顺序等问题,今天就抽空整理了一下这个知识点,然后记录下来. 正文 本篇文章主要是通过举例的方式来阐 ...
- python高阶-Linux基础命令集
声明: 1)仅作为个人学习,如有冒犯,告知速删! 2)不想误导,如有错误,不吝指教! 1: 查看文件信息:ls ls常用参数: 参数 含义 -a 显示指定目录下所有子目录与文件,包括隐藏文件 -l 以 ...
- 关于数据文件的文件头2-P2
文章目录 1 疑问点 2 实验验证 2.1 实验环境 2.2 创建统一区大小管理表空间 2.2.1 统一区大小40k 2.2.2 统一区大小56k 2.2.3 统一区大小64k 2.2.4 统一区大小 ...
- 每日一题 - 剑指 Offer 47. 礼物的最大价值
题目信息 时间: 2019-07-02 题目链接:Leetcode tag:动态规划 难易程度:中等 题目描述: 在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0). ...
- 常用API - 字符串
String类 java.lang.String类代表字符串 Java 程序中的所有字符串字面值(如 "abc" )都作为此类的实例实现. 特点 字符串的内容不可变!! 因为 St ...
- HDU 5961 传递 题解
题目 我们称一个有向图G是 传递的,当且仅当对任意三个不同的顶点a,,若G中有 一条边从a到b且有一条边从b到c ,则G中同样有一条边从a到c. 我们称图G是一个 竞赛图,当且仅当它是一个有向图且它的 ...
- python学习笔记之文件操作(三)
这篇博客小波主要介绍一下python对文件的操作 对文件的操作主要分为三步: 1.打开文件获取文件的句柄,句柄也是文件描述符 2.通过文件句柄操作文件 3.关闭文件. 现有以下文件,是小波随写的周杰伦 ...