元类

知识点

背景

  1. 元类的是一个特殊的类,主要目的就是为了当创建类时能够自动地改变类

  2. 对比装饰器可以在不改变方法内容的情况下丰富方法的功能,通过元类也可以在不修改类对象内容的情况下,丰富类对象中的属性和方法。

  3. Python中type是内建元类,通过它可以直接创建一个类对象,创建的格式如下:

    1
    type(类名, 由父类名称组成的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))

案例

  • 利用type创建带属性和方法的类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    # type赋值的对象,建议最好和type定义的第一个类名一致,(当然也可不一致)
    # type的第三个参数中参数,如果添加了属性,则其是类属性,不是实例属性
    # type的第三个参数中可以添加函数的键值关系
    def test(self):
    print("test")

    @classmethod
    def test_cls(cls):
    print("test")

    @staticmethod
    def test_static():
    print("test")

    Foochild = type('Foochild', (Foo,), {"name": "wuxiang", "test": test, "test_cls": test_cls, "test_static": test_static})
    print(Foochild.name)

    # 利用Foochild创建一个对象
    test_class = Foolchild()
    # 利用__class__可以查看创建当前对象的类是谁
    print(test_class.__class__)
  • 自定义元类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    # 你可以在定义一个类的时候为其添加__metaclass__属性
    # __metaclass__实际上可以被任意调用,它并不需要是一个正式的类,也可以是方法,但最后一定要返回一个type创建的类对象
    # 以方法为例
    def upper_attr(class_name, class_parents, class_attr):

    #遍历属性字典,把不是__开头的属性名字变为大写
    new_attr = {}
    for name,value in class_attr.items():
    if not name.startswith("__"):
    new_attr[name.upper()] = value

    #调用type来创建一个类
    return type(class_name, class_parents, new_attr)

    class Foo(object, metaclass=upper_attr):
    bar = 'bip'

    print(hasattr(Foo, 'bar'))
    print(hasattr(Foo, 'BAR'))
    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
    # 以类为例
    class UpperAttrMetaClass(type):
    # __new__ 是在__init__之前被调用的特殊方法
    # __new__是用来创建对象并返回之的方法
    # 而__init__只是用来将传入的参数初始化给对象
    # 你很少用到__new__,除非你希望能够控制对象的创建
    # 这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__
    # 如果你希望的话,你也可以在__init__中做些事情
    # 还有一些高级的用法会涉及到改写__call__特殊方法,但是我们这里不用
    def __new__(cls, class_name, class_parents, class_attr):
    # 遍历属性字典,把不是__开头的属性名字变为大写
    new_attr = {}
    for name, value in class_attr.items():
    if not name.startswith("__"):
    new_attr[name.upper()] = value

    # 方法1:通过'type'来做类对象的创建
    return type(class_name, class_parents, new_attr)

    # 方法2:复用type.__new__方法
    # 这就是基本的OOP编程,没什么魔法
    # return type.__new__(cls, class_name, class_parents, new_attr)

    # python3的用法
    class Foo(object, metaclass=UpperAttrMetaClass):
    bar = 'bip'

    # python2的用法
    # class Foo(object):
    # __metaclass__ = UpperAttrMetaClass
    # bar = 'bip'


    print(hasattr(Foo, 'bar'))
    # 输出: False
    print(hasattr(Foo, 'BAR'))
    # 输出:True

    f = Foo()
    print(f.BAR)

实际应用

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
class ModelMetaclass(type):
def __new__(cls, name, bases, attrs):
mappings = dict()
# 判断是否需要保存
for k, v in attrs.items():
# 判断是否是指定的StringField或者IntegerField的实例对象
if isinstance(v, tuple):
print('Found mapping: %s ==> %s' % (k, v))
mappings[k] = v

# 删除这些已经在字典中存储的属性
for k in mappings.keys():
attrs.pop(k)

# 将之前的uid/name/email/password以及对应的对象引用、类名字
attrs['__mappings__'] = mappings # 保存属性和列的映射关系
attrs['__table__'] = name # 假设表名和类名一致
return type.__new__(cls, name, bases, attrs)


class Model(object, metaclass=ModelMetaclass):
# 定义一个类时,程序会首先查找类中是否有属性__metaclass__,
# 如果有则直接用该属性指向的方法/类创建ModelMetaclass
# 如果当前类中没有则会向该类的父类中去查找属性__metaclass__,然后用该属性指向的方法/类去创建该类
# 如果其继承的所有父类都没有该属性,则其会在模块层次中去寻找属性__metaclass__,然后执行同样的操作
# 如果上述的寻找方式都找不到属性__metaclass__,则会调用Python内置的type来创建这个类对象
def __init__(self, **kwargs):
for name, value in kwargs.items():
setattr(self, name, value)

def save(self):
fields = []
args = []
for k, v in self.__mappings__.items():
fields.append(v[0])
args.append(getattr(self, k, None))

args_temp = list()
for temp in args:
# 判断入如果是数字类型
if isinstance(temp, int):
args_temp.append(str(temp))
elif isinstance(temp, str):
args_temp.append("""'%s'""" % temp)
sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(args_temp))
print('SQL: %s' % sql)


class User(Model):
uid = ('uid', "int unsigned")
name = ('username', "varchar(30)")
email = ('email', "varchar(30)")
password = ('password', "varchar(30)")


u = User(uid=12345, name='Michael', email='test@orm.org', password='my-pwd')
# print(u.__dict__)
u.save()

# 首先程序从上往下执行,遇到【class ModelMetaclass(type)】时,发现没有指定属性__metaclass__,则其直接调用type创建类对象
# 然后继续执行,运行到【class Model(object, metaclass=ModelMetaclass)】,发现其执行了属性__metaclass__,然后它会寻找ModMetaclass,然后通过ModeMetaclass指向的类/方法创建一个类对象
# 然后程序继续运行到【User(Model)】,没有寻找到属性__metaclass__,然后向其父类中寻找,发现有,则开始用其父类中的属性__metaclass__创建了一个类对象,创建类对象时,将User中的所有实例属性进行了转换