元类

面向对象中,对象是类的实例,即对象是通过类创建出来的,在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的更多相关文章

  1. Python学习_13_继承和元类

    继承 继承的含义就是子类继承父类的命名空间,子类中可以调用父类的属性和方法,由于命名空间的查找方式,当子类中定义和父类同名属性或者方法时,子类的实例调用的是子类中的属性,而不是父类,这就形成了pyth ...

  2. 通过 python的 __call__ 函数与元类 实现单例模式

    简单一句话,当一个类实现__call__方法时,这个类的实例就会变成可调用对象. 直接上测试代码 class ClassA: def __call__(self, *args, **kwargs): ...

  3. 【python进阶】详解元类及其应用1

    前言 元类在python中是很重要的一部分,我将分两次去讲解元类及其应用,此篇为详解元类及其应用第一篇,下面开始今天的说明~~~ 1. 类也是对象 在⼤多数编程语⾔中,类就是⼀组⽤来描述如何⽣成⼀个对 ...

  4. 【python进阶】详解元类及其应用2

    前言 在上一篇文章[python进阶]详解元类及其应用1中,我们提到了关于元类的一些前置知识,介绍了类对象,动态创建类,使用type创建类,这一节我们将继续接着上文来讲~~~ 5.使⽤type创建带有 ...

  5. python中的单例模式、元类

    单例模式 单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在.当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场. ...

  6. [转]深刻理解Python中的元类(metaclass)以及元类实现单例模式

    使用元类 深刻理解Python中的元类(metaclass)以及元类实现单例模式 在看一些框架源代码的过程中碰到很多元类的实例,看起来很吃力很晦涩:在看python cookbook中关于元类创建单例 ...

  7. python面试题~反射,元类,单例

    1 什么是反射?以及应用场景? test.py def f1(): print('f1') def f2(): print('f2') def f3(): print('f3') def f4(): ...

  8. 深刻理解Python中的元类(metaclass)以及元类实现单例模式

    在理解元类之前,你需要先掌握Python中的类.Python中类的概念借鉴于Smalltalk,这显得有些奇特.在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段.在Python中这一点仍 ...

  9. python(七):元类与抽象基类

    一.实例创建 在创建实例时,调用__new__方法和__init__方法,这两个方法在没有定义时,是自动调用了object来实现的.python3默认创建的类是继承了object. class A(o ...

  10. Python元类(metaclass)以及元类实现单例模式

    这里将一篇写的非常好的文章基本照搬过来吧,这是一篇在Stack overflow上很热的帖子,我看http://blog.jobbole.com/21351/这篇博客对其进行了翻译. 一.理解类也是对 ...

随机推荐

  1. Docker Playgrounds

    上级:https://www.cnblogs.com/hackerxiaoyon/p/12747387.html Flink Operations Playground flink的操作场地,从这一小 ...

  2. 为Linux主机安装图形化桌面环境

    本文主要介绍在Linux实例中,centos 7 以及ubutun 14如何安装图形化桌面环境. CentOS 7 此处以安装MATE桌面环境为例,步骤如下. 说明:在安装重启后,如果卡在启动页面,需 ...

  3. Illegal reflective access by org.apache.hadoop.security.authentication.util.KerberosUtil

    在使用Java API操作HBase时抛出如下异常: Illegal reflective access by org.apache.hadoop.security.authentication.ut ...

  4. spring quartz 每30分钟执行一次cronExpression表达式怎么写

      <cron-expression>0 0/30 * * * ?</cron-expression>:每隔30分钟 <cron-expression>0 0/15 ...

  5. 《JavaScript高级程序设计》(第二版)

    这本书的作者是 Nicholas C.Zakas ,博客地址是 http://www.nczonline.net/ ,大家可以去多关注,雅虎的前端工程师,是YUI的代码贡献者,可想而知这本书得含金量, ...

  6. 不就是语法和长难句吗—笔记总结Day2

    6.区别定语从句和同位语从句 I have a dream that sounds funny. (定语从句) I have a dream that I will become a rich man ...

  7. 每日一题 - 剑指 Offer 47. 礼物的最大价值

    题目信息 时间: 2019-07-02 题目链接:Leetcode tag:动态规划 难易程度:中等 题目描述: 在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0). ...

  8. 石子合并——区间dp

    石子合并(3种变形) <1> 题目: 有N堆石子排成一排(n<=100),现要将石子有次序地合并成一堆,规定每次只能选相邻的两堆合并成一堆,并将新的一堆的石子数,记为改次合并的得分, ...

  9. POJ - 3463 Sightseeing 最短路计数+次短路计数

    F - Sightseeing 传送门: POJ - 3463 分析 一句话题意:给你一个有向图,可能有重边,让你求从s到t最短路的条数,如果次短路的长度比最短路的长度多1,那么在加上次短路的条数. ...

  10. 通过注入DLL修改API代码实现钩取(一)

    通过注入DLL修改API代码实现钩取(一) Ox00 大致思路 通过CreateRemoteThread函数开辟新线程,并将DLL注入进去 通过GetProcessAddress函数找到需钩取的API ...