0%

Python中的元编程

元编程

什么是元编程?

用代码来生成代码,或者说,用程序来生成程序,就叫元编程。Python能通过反射实现元编程。

什么是元类?

与元编程相关的一个概念是元类。什么是元类呢,具体到Python,用来创建类的类,就叫元类。元类是制造类的工厂。

在Python中,一个普通类创建出来的东西是类的实例,实例是一个对象。而元类也是一种类,它创建出来的东西是另一个普通类,普通类也是一个对象(Python中一切皆对象),然后这个普通类,又可以创建出类的实例。所以说,元类是类的类。它们的构建过程如下图:

Python中:

1、所有非object类都继承自object类(包括type)
2、所有类的类型都是type(包括type、object、元类)
3、type类继承自object(符合第1句)

虽然type也继承自object,但是我们自己写的类,继承自object与继承自type,会有些不同。

type元类

type这个类,不仅可以获取Python中对象的类型,比如type(123),会告诉你123是int类型(int也是类)。

type也可以作为元类,来构建其他类。语法是:

type(name, bases, dict) -> a new type 返回一个新的类型

其中,name为类的名称,bases为类的继承列表,dict为类的属性字典。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def __init__(self):
self.x = 123

# 用type创建了一个新的类,变量名NewClass,新类的类名是newclass
NewClass = type('newclass', (object,), {'a':100, 'b':[], '__init__':__init__})
print(NewClass.__dict__) # 类的属性里会有a, b, __init__

# 等价于如下class关键字创建的类
class newclass(object):
a = 100
b = []
def __init__(self):
self.x = 456
print(newclass.__dict__)

# 不同之处在于,type创建的newclass的__init__函数在全局作用域里,
# class关键字创建的newclass的__init__函数在类内部,即newclass.__init__

以上例子没有什么神奇。元类的真正用途,是在元类的构造方法(__new__, __init__)里写我们的代码,从而改变一个类的构建行为。

上面用type构建了一个新的类,类在程序中是代码,type(…)调用也是代码。所以,这种用代码来生成代码的过程,就是元编程

构建自己的元类

如果一个类继承自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
39
40
41
42
43
44
45
46
47
# 创建元类,ModelMeta是元类,因为继承自type
# 我们把ModelMeta当作元类模版
class ModelMeta(type):

# 因为继承自type,参数与type元类一样
# 在__new__中可以改变构建普通类时的行为
def __new__(cls, name, bases, _dict):
print(cls)
print(name)
print(bases)
print(_dict)

# 注意这里和普通类调用super不同: return super().__new__(cls)
return super().__new__(cls, name, bases, _dict)
# 或者
#return type.__new__(cls, name, bases, _dict)

# 1、用metaclass关键字参数创建一个元类
# 这里的写法表示A这个类是被元类ModelMeta构建的,而不是继承
# 这种写法是用元类构建一个类A,所以元类里的__new__实例化方法会执行
# 即元类实例化出来的东西是另一个普通类(而不是一个类的实例)
# A是普通类
class A(metaclass=ModelMeta):
x = 999
def __init__(self):
print('A.init')

print('=' * 30)
# 2、继承
# 这里才表示正常的类的继承,
# 但是B的构建路线是:ModelMeta->构建A->继承到B
# B是普通类
class B(A):
def __init__(self):
print('B.init')

print('=' * 30)
# 3、元类模版也可以使用调用语法,创建新类,就像type那样
# C是普通类
C = ModelMeta('C', (), {'x':999})

print('=' * 30)
# 4、继承元类
# D继承自元类ModelMeta,所以D也是一个元类(这里是继承而不是用元类构建D)
# 因为是继承,元类ModelMeta的__new__不会执行
class D(ModelMeta):
pass

元类的应用

元编程可用于开发框架。比如ORM(对象关系映射)。将数据库的操作与类(以及实例)的操作联系起来,就可以使用元类。

映射关系:

表 ⟶ class
行 ⟶ 实例
字段 ⟶ 属性(描述器,见 这里

一个简单的例子:

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# 字段类
class Field:
# 字段名fn,类型tp,是否为主键pk,是否空null
def __init__(self, fn=None, tp=None, pk=False, null=True):
self.fn = fn
self.tp = tp
self.pk = pk
self.nl = null
self.value = None

def __get__(self, instance, owner):
return self.value

def __set__(self, instance, value):
if self.tp:
self.value = self.tp(value)

def __delete__(self, instance):
del self.value


# 模版元类
class ModelMeta(type):
def __new__(cls, name, bases, attrs):
#print(cls)
#print(name)
#print(bases)
#print(attrs)

# 添加表名
if '__tblname__' not in attrs.keys():
attrs['__tblname__'] = name

primarykeys = []
for k,v in attrs.items():
if isinstance(v, Field):
if v.fn is None:
v.fn = k # 属性名作为字段名
if v.pk:
primarykeys.append(v)
v.nl = False # 有主键自动不为空

attrs['__primarykeys__'] = primarykeys

return type.__new__(cls, name, bases, attrs)


# 基类
class Base(metaclass=ModelMeta):
def __init__(self, **kwargs):
for k,v in kwargs.items():
if k not in self.__class__.__dict__:
raise AttributeError(f"'{k}' is not a field name")
setattr(self, k, v) # 将调用描述器的__set__


# 设计学生表
class Student(Base):
sid = Field(tp=int, pk=True) # 描述器
name = Field('username', tp=str, null=False)
age = Field(tp=int)

def __repr__(self):
return f"<{self.__class__.__name__} sid={self.sid} name={self.name} age={self.age}>"


# s = Student(sid=1, gender=112)
s = Student(sid=1)
print(s)

print('=' * 30)
s.name = 123
s.age=20
print(s)

print('=' * 30)
print(s.name, s.age, type(s.name))