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/这篇博客对其进行了翻译. 一.理解类也是对 ...
随机推荐
- android 6.0 权限设置详解
从Android 6.0版本开始,在安装应用时,该应用无法取得任何权限. 相反,在使用应用的过程中,若某个功能需要获取某个权限,系统会弹出一个对话框,显式地由用户决定是否将该权限赋予应用. 只有得到了 ...
- Docker 基础知识 - Docker 概述
Docker 是一个开发.发布和运行应用程序的开放平台.Docker使您能够将应用程序与基础架构分离,以便快速交付软件.有了 Docker,你可以像管理应用程序一样管理你的基础设施.通过利用 Dock ...
- js事件入门(6)
7.事件冒泡机制 7.1.什么是事件冒泡 当一个元素接收到一个事件以后,会将事件传播给它的父级元素,它的负级元素会一层一层往上传播,直到最顶层window,这种事件传播机制叫作事件冒泡. <!D ...
- 使用 Nginx 部署静态页面
Nginx 介绍 Nginx 是俄罗斯人编写的十分轻量级的 HTTP 服务器, Nginx,它的发音为「engine X」,是一个高性能的 HTTP 和反向代理服务器,同时也是一个 IMAP/ POP ...
- Tornado的使用
Tornado 和现在的主流 Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快.得利于其非阻塞的方式和对 epoll 的运用 基本操作 torn ...
- Dubbo远程调用之公司内部提供的服务
公司内部提供的服务 一家对外提供服务的公司,例如百度,腾讯,阿里,京东,58 同城等,公司内部有多个事业群,事业部门,每个事业部门内部又有若干个子部门,子部门里面有多个不同的小组负责各自的业务.提供对 ...
- css3实现背景颜色渐变,文字颜色渐变,边框颜色渐变
css3的渐变可以使用2个或者多个指定的颜色之间显示平稳的过渡的效果.这篇文章主要介绍下css3实现背景颜色渐变,文字颜色渐变,边框颜色渐变的方法,以便大家学习参考! 1.css背景颜色渐变 代码: ...
- 【Java8新特性】冰河带你看尽Java8新特性,你想要的都在这儿了!!(文本有福利)
写在前面 很多小伙伴留言说,冰河你能不能写一些关于Java8的文章呢,看书看不下去,看视频进度太慢.好吧,看到不少读者对Java8还是比较陌生的,那我就写一些关于Java8的文章吧,希望对大家有所帮助 ...
- 赞!7000 字学习笔记,一天搞定 MySQL
MySQL数据库简介 MySQL近两年一直稳居第二,随时有可能超过Oracle计晋升为第一名,因为MySQL的性能一直在被优化,同时安全机制也是逐渐成熟,更重要的是开源免费的. MySQL是一种关系数 ...
- python入门006
一:可变与不可变类型 可变类型:值改变,id不变,证明改的是原值,证明原值是可以被改变的 不可变类型:值改变,id也变了,证明是产生新的值,压根没有改变原值,证明原值是不可以被修改的 2.验证 2.1 ...