使用type函数构造类

在python可以通过type函数构造类。

1
2
3
4
5
6
7
8
MyClass = type("MyClass", (object,), {"x": 42, "func": lambda self: self.x * 2})


class MyClass2:
    x = 42

    def func(self):
        return self.x * 2

以上两种方式的效果是一样的,都构造了MyClass类,并且含有属性x,以及类方法func

类装饰器

在python中的类装饰器和函数装饰器用法相同,只不过被装饰的对象变成了类。原本的类经过类装饰器装饰后,可以改变原始类中的属性或方法,返回新的类。

 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
# 类装饰器
def Entity(cls):
    for name, attr in cls.__dict__.items():
        if isinstance(attr, Validated):
            type_name = type(attr).__name__
            attr.storage_name = "_{}#{}".format(type_name, name)
    return cls


@Entity
class LineItem:
    descript = NonBlank()  # 商品描述属性为不能为空的描述类
    weight = Quantity()  # 使用新的描述类代替
    price = Quantity()

    def __init__(self, description, weight, price):
        self.descript = description
        self.weight = weight
        self.price = price

    def Subtotal(self):
        return self.weight * self.price


raisins = LineItem('Golden raisins', 10, 6.0)

# 未使用装饰器打印:{'_NonBlank#0': 'Golden raisins', '_Quantity#0': 10, '_Quantity#1': 6.0}
# 使用装饰器后打印:{'_NonBlank#descript': 'Golden raisins', '_Quantity#weight': 10, '_Quantity#price': 6.0}
print(raisins.__dict__)

使用20章中的LineItem类为例,原本类中__dict__中保存的属性为_NonBlank#0命名方式,使用数字作为属性的标识,经过装饰器装饰后,可以通过原本属性的名称进行标识。

导入时与运行时

 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
print("<[1]> Import and runtime moudle start")


class ClassOne:
    print("<[2]> ClassOne body")

    def __init__(self):
        print("<[3]> ClassOne.__init__")

    def __del__(self):
        print("<[4] ClassOne.__del__>")

    def MethodX(self):
        print("<[5] ClassOne.MethodX>")

    class ClassTwo:
        print("<[6]> ClassTwo body")


import evalsupport


@evalsupport.ClsDecorator
class ClassThree:
    print("<[7]> ClassThree body")

    def MethodY(self):
        print("<[8] ClassThree.MethodY>")


class ClassFour(ClassThree):
    print("<[9]> ClassFour body")

    def MethodY(self):
        print("<[10] ClassFour.MethodY>")


class ClassFive(metaclass=evalsupport.MetaAleph):
    print("<[11]> ClassFive body")

    def __init__(self):
        print("<[12]> ClassFive.__init__")

    def MethonZ(self):
        print("<[13]> ClassFive.MethonZ")


class ClassSix(ClassFive):
    print("<[14] ClassSix body>")

    def MethonZ(self):
        print("<[15] ClassSix.MethonZ>")


if __name__ == "__main__":
    print("<[16]> ClassOne tests", 30 * "*")
    one = ClassOne()
    one.MethodX()

    print("<[17]> ClassThree tests", 30 * "*")
    three = ClassThree()
    three.MethodY()

    print("<[18]> ClassFour tests", 30 * "*")
    four = ClassFour()
    four.MethodY()

    print("<[19]> ClassFive tests", 30 * "*")
    five = ClassFive()
    five.MethodZ()

    print("<[20]> ClassFive tests", 30 * "*")
    six = ClassSix()
    six.MethodZ()

print("<[21]> Import and runtime moudle end")

evalsupport.py中的内容如下:

 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
print("<[100]> Evalsupport moudle start")


def ClsDecorator(cls):
    print("<[200]> Class Decorator body")

    def Method(self):
        print("<[300]> Class method changes")

    cls.MethodY = Method
    return cls


class MetaAleph(type):
    print("<[400]> MetaAleph body")

    def __init__(cls, name, bases, dic):
        print("<[500]> MetaAleph.__init__")

        def MethodZ(self):
            print("<[600]> MetaAleph.MethodZ")

        cls.MethodZ = MethodZ


print("<[700]> Evalsupport moudle end")

最终打印出来的结果如下:

 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
<[1]> Import and runtime moudle start       # 模块开始
<[2]> ClassOne body     # 对于类对象,类中的定义体运行了
<[6]> ClassTwo body     # 类中的类对象的定义体也会运行,不管嵌套多少层
<[100]> Evalsupport moudle start    # 开始导入Evalsupport模块
<[400]> MetaAleph body      # 导入模块中的类对象的定义体运行
<[700]> Evalsupport moudle end  # 导入模块结束
<[7]> ClassThree body       # 使用装饰器的类,先运行原本类中的定义体...
<[200]> Class Decorator body    # ...再运行装饰器函数
<[9]> ClassFour body    # 继承的父类对象的定义体不会再次运行
<[11]> ClassFive body   # 通过元类继承,先运行子类的定义体
<[500]> MetaAleph.__init__  # 元类中的__init__方法相当于是类装饰器...
<[14] ClassSix body>
<[500]> MetaAleph.__init__  # ...不同的是,元类的初始化方法对于所有的子类都有效
<[16]> ClassOne tests ******************************
<[3]> ClassOne.__init__ # 运行类的初始化实例
<[5] ClassOne.MethodX>  # 运行类的方法
<[17]> ClassThree tests ******************************
<[300]> Class method changes    # 使用装饰器改变了类的方法
<[18]> ClassFour tests ******************************
<[10] ClassFour.MethodY>    # 子类的同名方法覆盖了父类的同名方法,如果没有覆盖,被装饰的类中的方法会被子类继承
<[19]> ClassFive tests ******************************
<[12]> ClassFive.__init__
<[600]> MetaAleph.MethodZ   # 由于继承了元类,导致子类的对应方法都改变成了元类中的初始化改变值
<[20]> ClassFive tests ******************************
<[12]> ClassFive.__init__
<[600]> MetaAleph.MethodZ
<[21]> Import and runtime moudle end    # 模块结束
<[4] ClassOne.__del__>  # 销毁实例对象

在python模块导入和运行时要注意的是函数和类的不同,函数中的定义体在导入时是不会运行的,而类中的定义体(非类中方法内的定义体),在导入时可以运行。

如果继承的类位于导入的模块中,因为在导入时,类中的定义体已经被运行,所以作为父类时,父类的定义体不再运行。

类装饰器元类的区别:通过类装饰器修改过的方法,子类中可以通过覆盖的方式修改父类的同名方法,但通过元类的方式,子类中的同名方法只能是元类中初始化后指定的方法。