metaclass一直被归类为比较高深的内容,写代码也很少会使用到metaclass。然而如果你看某些项目的源代码,还是会被绕到metaclass里面去。花一些时间理解下metaclass很有用的,因为它真的能把一条线串起来,环环相扣,所以有扎实的基础是掌握metaclass的前提,本文代码基于python3.5

你只需要理解以下2点

  1. metaclass和__new____init__并没有非常非常深的关系
  2. type(name, bases, dict)生成了一个object类,了解三个参数分别指代了什么

了解python中有这样一条关系链type->object->instance,通常我们写类默认会继承自object。从关系链就能知道type能够生成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
28
29
30
31
32
33
34
35
36
37
38
class A(type):
def __new__(cls, name, bases, attrs):
attrs.pop('e')
print('1', cls, name, bases, attrs)
cls.a = 1
return super().__new__(cls, name, bases, attrs)
# def __call__(self, *args, **kwargs):
# print('3')
def __init__(self, name, bases, attrs):
self.b = 2
print('2', self, name, bases, attrs)
class B(metaclass=A):
def __new__(cls, *args, **kwargs):
print('4', args, kwargs)
cls.c = 4
return super().__new__(cls)
def e(self):
pass
def __call__(self, *args, **kwargs):
print('5')
def __init__(self, d):
self.d = d
print('6')
b = B(6)
print(hasattr(b, 'a'))
print(b.b)
print(b.c)
print(b.d)
print(hasattr(b, 'e'))

可以尝试注释掉B(12),A依旧会被打印,观察args和kwargs的输出。对比A和B的不同,你会发现metaclass的用处,使用metaclass的时候,B的所有属性会被当做参数传递给A,那么我们能够将这些传过来的参数预处理然后挂在到类的属性上面返回。以供后续B使用。目前在我遇到的web开发中体现的最常见的就是ORM和表单验证。

我们知道__new__创建一个对象。__init__对创建后的对象进行赋值等初始化操作。他们的传参args和kwargs是没有区别的。但是在__new__阶段,能够对attrs进行修改从而hook对象的创建流程,除此之外感觉只能用new不能用init的场景是很少的。目前看到的例子记得的可能只有用new实现单例。对于内置不可变对象比如int用new变更初始化逻辑

最后例举一个ORM的例子(见参考)

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class Field(object):
def __init__(self, name, column_type):
self.name = name
self.column_type = column_type
class StringField(Field):
def __init__(self, name):
super(StringField, self).__init__(name, 'vachar(100)')
class ModelMetaclass(type):
def __new__(cls, name, bases, attrs):
if name != 'Model':
mappings = {k: v.name for k, v in attrs.items() if isinstance(v, Field)}
for k in mappings.keys():
attrs.pop(k)
attrs['__mappings__'] = mappings
attrs['__table__'] = name
return type.__new__(cls, name, bases, attrs)
class Model(dict, metaclass=ModelMetaclass):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(r"'Model' object has no attribute {key}".format(key=key))
def __setattr__(self, key, value):
self[key] = value
def save(self):
fields, params, args = [], [], []
for k, v in self.__mappings__.items():
fields.append(v)
params.append('?')
args.append(getattr(self, k, None))
sql = 'insert into {table_name} ({fields}) values ({values})'.format(table_name=self.__table__,
fields=','.join(fields),
values=','.join(params))
print('SQL: {sql}'.format(sql=sql))
print('ARGS: {args}'.format(args=args))
class UserModel(Model):
user_name = StringField('username')
email = StringField('email')
password = StringField('password')
if __name__ == '__main__':
user = UserModel(user_id=123, user_name='hhh', email='a@a.a', password='asdfa', x='y')
user.save()

下篇会分析metaclass在WTForms中的应用

参考

Understanding Python metaclasses 更详细的介绍
python实现ORM