python的元类使用场景一般在大型框架里面,例如Django的ORM框架、基于python实现的高级设计模式,元类的这部分内容相对晦涩,但也是作为python非常核心的知识点,通过解析其机制,有利于阅读和学习优秀中间件源代码的设计逻辑,在面向对象设计的重要性不言而喻。本博客后面的内容将会给出较为复杂的设计模式的文章,里面会出现较多的元类编程,因此有必要单独开一篇文章讨论python元类,相关内容将参考Stack Overflow上一篇很受欢迎的关于python metaclasses的文章:what-are-metaclasses-in-python
1、python的class对象
1.1 python的class也是一种object
”在python的世界里,一切皆对象(object)“,这句话经常出现在很多python书籍中有关”面向对象或者类“文章里。如果你要深入python,首先面向对象的思维和面向对象的编程经历较为丰富。掌握对类的理解和运用,是理解元类的重要基础。
1.1 类即对象1
2
3
4
5
6In [1]: class Foo(object):
...: pass
...:
In [2]: my_instance=Foo()
In [3]: print(my_instance)
<__main__.Foo object at 0x108561b00>
这里创建了一个名为Foo的类,打印它的实例,可以看到该实例是一个Foo object 存放在内存地址:0x108561b00
这里只是说明Foo类的实例是object,怎么确认Foo是一个object呢?
两种方式可以回答:
方式一:在定义阶段:Foo(object),Foo这个类继承object,所以Foo是object
方式二:1
2In [8]: isinstance(Foo,object)
Out[8]: True
既然Foo是一个object,那么对于该object则可以扩展其功能:
- 可赋值给变量
可被复制
- 可添加属性
- 可将其当作函数参数传递
- 当然也可绑定新的类或者对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28In [13]: NewFoo=Foo
In [14]: print(NewFoo)
<class '__main__.Foo'>
In [16]: CloneFoo=copy.deepcopy(Foo)
In [17]: print(CloneFoo)
<class '__main__.Foo'>
In [20]: Foo.new_attr='bar'
In [21]: Foo.new_attr
Out[21]: 'bar
In [22]: def myfunc(obj):
...: print(obj.__name__)
...:
In [23]: myfunc(Foo)
Foo
In [24]: class NewFoo(object):
...: pass
...:
In [25]: Foo.x=NewFoo
In [26]: Foo.x
Out[26]: __main__.NewFoo总之,只要拿到一个object,你可以对其扩展任意你想得到效果
1.2 动态创建类
什么是动态创建类?只有运行这个程序后,通过判断给定参数来决定创建的是类A还是类B,而不是给程序写为固定生产类A。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15In [28]: def choose_class(which):
...: if which =='Foo':
...: class Foo(object):
...: pass
...: return Foo
...: elif which == 'Bar':
...: class Bar(object):
...: pass
...: return Bar
...:
...:
In [29]: myclass=choose_class('Bar')
In [32]: print(myclass.__name__)
Bar
前面提到,既然Foo创建一个实例就是一个对象,把这个逻辑放在Foo身上:既然(某某某)创建一个对象就是一个Foo类,这个某某某是什么?可以做什么?
这个某某某就是type这个内建函数(函数也是一个对象),用type也可以像上面一样动态的创建一个类,用法:1
2
3type(要创建的类名,该类的所有父类名字组成的元组(若无父类,则为空元组),要创建该类需要用到入参:属性的字典)
一般写成:
type(class_name,class_bases,class_dict)
经典方式一般如下:1
2
3
4
5
6
7
8
9class Person(object):
car='Model 3'
def __init__(self,name,age):
self.name=age
self.age=age
def info(self):
print('my name is {} and {} years old'.format(self.name,self.age))
使用type函数动态创建以上Person类的过程:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25# 因为Person继承object,所以type的第二个位置参数为(object,),Person类有三个属性因此class_dict为{'car':car,'__init__':__init__,'info':info})
car = 'Model 3'
def __init__(self,name,age):
self.name = name
self.age = age
def info(self):
print(self.name,self.age)
Person = type('Person',(object,),{'car':car,'__init__':__init__,'info':info})
In [3]: Person.__dict__
Out[3]:
mappingproxy({'car': 'Model 3',
'__init__': <function __main__.__init__(self, name, age)>,
'info': <function __main__.info(self)>,
'__module__': '__main__',
'__dict__': <attribute '__dict__' of 'Person' objects>,
'__weakref__': <attribute '__weakref__' of 'Person' objects>,
'__doc__': None})
In [5]: person = Person('Watt',20)
In [6]: person
Out[6]: <__main__.Person at 0x10896f9e8>
type创建完整Person类!本章内容主要通过类的创建,因此type这个函数并用其实现动态创建类,为元类这个话题做了铺垫,通过以上type创建的实例推出,python创建类必须要具备以下三个参数:
- 1、类名class_name
- 2、继承关系class_bases
- 3、类的名称空间class_dict
这三个参数是揭开元类是如何改变类的秘密。
2 、Python的metaclass元类
2.1 认识type
前面的内容已经说明Python中的类也是对象,那么metaclass元类(元类自己也是对象)就是用来创建这些类的类,例如可以这样理解:1
2MyClass = MetaClass() #元类创建了类
MyObject = MyClass() #被元类创建的类后,用它创建了实例
在上一节内容,type可创建MyClass类:MyClass = type('MyClass', (), {})
MyClass是type()这个特殊类的一个实例,只不过这个实例直接就是类。
以上的逻辑主要说明一件事:type这个特殊类,就是python的一个元类,type是Python在背后用来创建所有类的元类,这句话如何理解?
首先,还是那句熟悉的话:在python的世界里,一切皆对象(object),包括各类数据结构、函数、类以及元类,它们都来源于一个“创物者”,这个强大的创物者这就是type元类。
查看每种对象的__class__
属性:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24In [55]: num=10
In [56]: num.__class__
Out[56]: int
In [58]: alist=['a','c']
In [59]: alist.__class__
Out[59]: list
In [60]: def foo():
...: pass
...:
In [61]: foo.__class__
Out[61]: function
In [62]: class Bar(object):
...: pass
...:
In [64]: b=Bar()
In [65]: b.__class__
Out[65]: __main__.Bar
说明每个对象都是某种类,
那么,一个__class__
.__class__
又是属于哪种类呢?1
2
3
4
5
6
7
8
9
10
11In [69]: num.__class__.__class__
Out[69]: type
In [70]: foo.__class__.__class__
Out[70]: type
In [71]: alist.__class__.__class__
Out[71]: type
In [72]: b.__class__.__class__
Out[72]: type
在继续往“创物者”方向靠近,发现最后都是type:1
2
3
4
5In [74]: foo.__class__.__class__.__class__
Out[74]: type
In [75]: foo.__class__.__class__.__class__.__class__
Out[75]: type
以上说明:python的各类数据结构、函数、类以及元类,它们都来源于一个“创物者”,这个强大的创物者这就是type元类。
2.2 认识__metaclass__属性
在python的元类的设计中,通常会出现__metaclass__
属性,一般用法如下:1
2
3
4
5class Foo(object): #python2版本的写法
__metaclass__ = something…
class Foo(metaclass=something): #python3版本的写法
__metaclass__ = something…
当一个类的内部属性定义了__metaclass__
属性,说明这个类将由某个元类来创建,当Foo类一旦被调用,因为设计类时可能有继承关系,因此会出现属性搜索过程:
1)Foo的定义里面有__metaclass__
这个属性?如果有,解释器在内存中通过something...
这个元类创建一个名字为Foo的类(对象)
2)如果在Foo的作用域内未找到__metaclass__
属性,则继续在父类中寻找,若在父类找到,则用something...
这个元类创建一个名字为Foo的类(对象)。
3)如果任何父类中都找不到__metaclass__
属性,它就会在模块层次中去寻找__metaclass__
,若找到,则用something...
这个元类创建一个名字为Foo的类(对象)。
4)如果还是找不到__metaclass__
,解释器最终使用内置的type来创建这个Foo类对象。
从上面过程可知,既然找到something...
这个元类后它就可以创建类,说明它与type这个终极元类作用一样:都是用来创建类。
所以可推出:__metaclass__
指向某个跟type功能相仿的元类———任何封装type的元类、继承type的子类、type本身
下面用元类实现的redis连接单例来感受下以上的逻辑:
1 | class RedisMetaSingleton(type): |
测试其实例是否为单例:1
2
3
4
5
6
7In [37]: r1=RedisSingleton('182.0.0.10','6379',0)
In [38]: r1
Out[38]: <__main__.RedisSingleton at 0x10fdc6080>
In [39]: r2=RedisSingleton('182.0.0.10','6379',0)
In [40]: r1 is r2
Out[40]: True
将单例逻辑放在定义元类这里,其他redis常用方法则放在子类实现。此外,该测试用例需要注意的两点:
1)RedisMetaSingleton的__init__
和__call__
第一个参数为cls,表示元类要创建的”类对象“,因此用cls而不是self。元类至于类对象(mataclass==>class object),就像类至于实例(class==>instance),反复理解该句。
2)__init__(cls,class_name,class_bases,class_dict)
,第2个到4个参数,其实就是type元类创建类的所需参数:
type(类名,父类元组(若无父类,则为空元组),类属性或内部方法的字典)
3)由于RedisMetaSingleton继承type,那么super(RedisMetaSingleton, cls)经过搜索后,父类就是type,因此
A: super(RedisMetaSingleton, cls).__init__(class_name, class_bases, class_dict)
的初始化就等价于type.__init__(class_name, class_bases, class_dict)
的初始化
B:super(RedisMetaSingleton, cls).__call__(host,port,db)
创建类对象就等价于type.__call__(host,port,db)创建类对象
这就说明RedisSingleton指定由RedisMetaSingleton来创建,在RedisMetaSingleton内部最后交由type.__init__
初始化,证明过程如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22In [119]: class RedisMetaSingleton(type):
...: """在元类层面实现单例"""
...: def __init__(cls,class_name,class_bases,class_dict):
...: super(RedisMetaSingleton, cls).__init__(class_name, class_bases, class_dict)
...: print('class_name:{} class_bases:{} class_dict:{}'.format(class_name,class_bases,class_dict))
...: cls.cls_object =None
...:
...: def __call__(cls,host,port,db):
...: if not cls.cls_object:
...: cls.cls_object = super(RedisMetaSingleton, cls).__call__(host,port,db)
...: return cls.cls_object
...:
...: class RedisSingleton(metaclass=RedisMetaSingleton):
...: "redis操作专用类"
...: def __init__(self,host,port,db):
...: self.host=host
...: self.port=port
...: self.db=db
...: def conn(self):
...: pass
...:
当以上代码在ipython解释器敲下去后,解释器对RedisMetaSingleton做了__init__
初始化工作,故可得到以下打印信息1
2
3class_name:RedisSingleton # 类名
class_bases:() # 父类元组
class_dict:{'__module__': '__main__', '__qualname__': 'RedisSingleton', '__doc__': 'redis操作专用类', '__init__': <function RedisSingleton.__init__ at 0x10ffa0158>, 'conn': <function RedisSingleton.conn at 0x10ffa00d0>} # 要创建类的所有属性字典
这三个参数就是type创建类的所需的参数:type(类名,父类元组(若无父类,则为空元组),类属性或内部方法的字典)
以上内容略显复杂:归结起来,只要一个普通类指定需要元类创建,那么最终一定是由type这个终极元类来创建。
2.3 自定义元类
对元类的构建和原理有一定认识后,那么可通过元类定制普通类,真正站在创物者的上帝视野来创建普通类。
现在有这样一个需求,要求创建的普通类的属性满足以下条件:
对于开头不是__
的属性,都要大写,例如getname(self),在元类创建该普通类后都会被改为GETNAME(self)
开头为__
的属性,大小写保持不变。
从type创建普通类的“公式“可知:type(classname,classbases,classdict),classdict就是放置了普通类属性或内部方法的字典),故只需要对其修改后,再重新传入type即可实现,需要基于type的__new__
方法实现,type当然有`__new方法,因为type是元类,也是类。(元类必然有
__new方法,它创建的普通类例如Person、RedisConn才有这个内建的
__new方法。)
具体实现:
return type(class_name,class_bases, uppercase_attr)`的写法不是pythone的OOP的写法(不够高级、抽象),因此又转化为以下OOP写法:1
2
3
4
5
6
7
8
9
10
11class UpperAttrMetaclass(type):
# 在这里,被创建的对象是类,因此第一个参数为cls,而不是类实例化的self,且需重写__new__方法
def __new__(cls, class_name,class_bases,class_dict):
uppercase_attr = {}
for name, val in class_dict.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
# 用uppercase_attr替换了原class_dict,再传入到type,由type创建类,实现了自定义创建类的目标
return type(class_name,class_bases, uppercase_attr)
1 | class UpperAttrMetaclass(type): |
以上OOP风格在知名的python框架中到处可见!此外,我们知道通过super(UpperAttrMetaclass,cls)可以搜索到父类type,因此开发者会习惯写成以下形式:
1 | class UpperAttrMetaclass(type): |
定义一个普通类测试:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19In [124]: class Person(metaclass=UpperAttrMetaclass):
...: def __init__(self,name,age):
...: self.name=name
...: self.age=age
...: def get_name(self):
...: print('name is:',self.name)
...: def get_age(self):
...: print('age is:',self.age)
...:
In [125]: Person.__dict__
Out[125]:
mappingproxy({'GET_NAME': <function __main__.Person.get_name(self)>,
'GET_AGE': <function __main__.Person.get_age(self)>,
'__module__': '__main__',
'__dict__': <attribute '__dict__' of 'Person' objects>,
'__weakref__': <attribute '__weakref__' of 'Person' objects>,
'__doc__': None})
当普通类Person定义后,解释器已经用元类UpperAttrMetaclass创建了Person普通类,其get_name和get_age方法名都被改为大写:GET_NAME和GET_AGE,其他双下划线的的方法名字保持不变。
此外,metaclass不局限于类的调用,也可以在任何对象内部调用,例如函数内部调用,例如以下一个模块upper_attr_by_func.py:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21def upper_attr(class_name,class_bases,class_dict):
uppercase_attr = {}
for name, val in class_dict.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return type(class_name,class_bases,class_dict)
__metaclass__ = upper_attr # 该元类只能作用在本模块的所有类,对其他模块a.py、b.py无影响。
class Person(metaclass=UpperAttrMetaclass):
def __init__(self,name,age):
self.name=name
self.age=age
def get_name(self):
print('name is:',self.name)
def get_age(self):
print('age is:',self.age)
但需要注意的是:这种方式,元类的作用域将受到限制,仅能影响本模块upper_attr_by_func.py的所有类,对其他模块的类不产生作用。
综上,可总结元类定制普通类的创建一般如下过程:
- 拦截一个普通类,一般在会使用
__new__,__init__ 和 __call__
,这些方法的内部可以放入对普通类进行不同定制的代码逻辑,其中:
A、__new__
和__init__
方法用于控制类的行为
B、__call__
方法用于控制类实例化的行为( - 修改普通类,一般是指修改type(classname,classbases,class_dict)里面的三个参数,尤其对class_dict修改频繁,例如要求class_dict里面的必须要有`__doc`属性,甚至针对父类元组class_bases来操作其继承关系。
- 通过
return super().__new__(cls,class_name,custom_bases, custom_class_dict)
创建并返回普通类
元类一般用于复杂的框架上改变类的行为,对于普通简单的类,还有其他两种手段用来改变类:
- monkey patching
- 类装饰器
按Stack Overflow上的”建议“:
如果需要改变类,99%的情况下使用这两种方法,但其实98%的情况你根本不需要改变类。所以你看到很多较为简单的python轮子,一般是几个普通类就可以完成,根本无需动用元类来构建普通类。
3、元类定制普通类的示例——myORM
本节内容参考了廖雪峰的文章,但其文章有很多关键的语句并无做更细致的说明,本节内容会在重要的元类实现逻辑上给出更详细的文字说明。
这里将实现一个轻量ORM——myORM:
在架构层面(不面向用户)
- 架构定义了一个元类ModelMetaClass,用于拦截和修改普通类User定义阶段的class_dict属性字典,并用改造后的class_dict传入type来创建普通类User对象。
- 架构定义了一个Model类,用于把字段属性名和字段值封装在拼接的SQL语句,主要负责与数据库的增删查改。
- 架构定义了一个基本字段类Field:包含字段名和字段类型
在用户层面(面向用户,用户可自行定义各种模型)
- 用户定义一个整数字段类IntField,用于存放整型类型数据,例如id号,age
- 用户定义一个字符串字段类CharField,用于存放字符类型数据,例如name,email
- 创建一个User模型的一条行记录
1 |
|
ModelMetaClass负责顶层设计(改造),用户创建所有的普通类如User、Article、Department等,都会被该元类重设设计(改造它们的class_dict)后再创建出这些普通类。
用户定义了一个User模型,有四个字段,并指定创建为表名为USER_T1
2
3
4
5
6
7
8
9
10
11
12
13class User(Model):
id=IntField('user_id')
name=CharField('user_name')
email=CharField('email',max_length=200)
password=CharField('password')
class Meta:
"""
自定义数据库表名,这里虽然Meta定义为类,
但在元类ModelMetaClass的视角来看,它是一个属性,放在class_dict里面
"""
db_table='USER_T'
当用户定义完以上的普通类User后,Python解释器首先在当前类User的定义中查找metaclass,显然当前上下文环境没有找到,则继续在父类Model中查找metaclass,发现Model定义了metaclass=ModelMetaClass,故直接交由ModelMetaclass来创建该普通的User类。
用户创建了User实例并尝试向db插入该条数据1
2
3
4
5u=User(id=1,name='Wott',email='11@11.com',password='1213') # Model继承dict,因此Model子类User当然可用字典创建方式来创建实例
u['name']='Foo'# Model继承dict,因此Model子类User当然可使用字典方式赋值
u.password='Pa33Wood'# Model内部定义__setattr__方法,故可用点号给属性赋值
print(u.email) # Model内部定义__getattr__方法,故可用点号取属性值
u.save()
以上代码各个位置上的print输出结果如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30第一句print输出:
在ModelMetaClass元类捕获到普通类的属性字典:
{'__module__': '__main__', '__qualname__': 'User', 'id': IntField<user_id,bigint>, 'name': CharField<user_name,varchar(100)>, 'email': CharField<email,varchar(200)>, 'password': CharField<password,varchar(100)>, 'Meta': <class '__main__.User.Meta'>}
第二句print输出:
attr_name is "id",field object is "<class '__main__.IntField'>"
attr_name is "name",field object is "<class '__main__.CharField'>"
attr_name is "email",field object is "<class '__main__.CharField'>"
attr_name is "password",field object is "<class '__main__.CharField'>"
第三句print输出:
User模型定义的Meta属性: <class '__main__.User.Meta'>
第四句print输出:
User模型在Meta指定的数据库表名为: USER_T
第五句print输出:
11@11.com
第六句print输出:
Model type:{'id': 1, 'name': 'Foo', 'email': '11@11.com', 'password': 'Pa33Wood'}
Model type:{'id': 1, 'name': 'Foo', 'email': '11@11.com', 'password': 'Pa33Wood'}
Model type:{'id': 1, 'name': 'Foo', 'email': '11@11.com', 'password': 'Pa33Wood'}
Model type:{'id': 1, 'name': 'Foo', 'email': '11@11.com', 'password': 'Pa33Wood'}
第七句print输出:
SQL语句: insert into USER_T (user_id,user_name,email,password) values (%s,%s,%s,%s)
第八句print输出:
SQL的入参值列表: [1, 'Foo', '11@11.com', 'Pa33Wood']