python面向对象:元类

本文介绍了Python中的`exec`命令用法,元类的概念及其在类创建中的作用,包括如何自定义元类以控制类的行为,如文档注释验证、类名格式检查、实例化过程和单例模式实现。最后提供了两个练习题,展示如何在元类中进行数据属性转换和限制构造函数参数形式。
摘要由CSDN通过智能技术生成

一 知识储备

1

2

3

4

5

6

7

exec:三个参数

参数一:字符串形式的命令

参数二:全局作用域(字典形式)

参数三:局部作用域(字典形式)

  exec的使用

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

# 可以把exec命令的执行当成是一个函数的执行,会将执行期间产生的名字存放于局部名称空间中

= {

    'x'1,

    'y'2

}

= {}

exec('''

global x, z

x = 100

z = 200

m = 300

''', g, l)

print(g)           # {'x': 100, 'y': 2,'z':200,......}

print(l)            #{'m': 300}

  

二  引子(类也是对象)

1

2

3

4

class Foo:

    pass

f1 = Foo()  # f1 是通过Foo()实例化对象

  

  python中一切皆是对象,类本身也是一个对象,当使用关键字class的时候,python解释器在加载class的时候就会创建一个对象(这里的对象指的是类而非类的实例),因而我们可以将类当作一个对象去使用,同样满足第一类对象的概念,可以:

把类赋值给一个变量

把类作为函数的返回值

在运行时动态地创建类

上例可以看出f1是由Foo这个类产生的对象,而Foo本身也是对象,那它又是哪个类产生的呢?

1

2

3

# type函数可以查看类型,也可以用来查看对象的类,二者是一样的

print(type(f1)) # 输出:<class '__main__.Foo'> 表示,obj 对象由Foo类创建

print(type(Foo)) # 输出:<type 'type'>

  

三 什么是元类?

 元类是类的类,是类的模板

元类是用来控制如何创建类的,正如类是创建对象的模板一样,而元类的主要目的是为了控制类的创建行为

 元类的实例化的结果为我们用class定义的类,正如类的实例为对象(f1对象是Foo类的一个实例,Foo类是type类的一个实例)

 type是python的内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象

四 创建类的两种方式

方式一:使用class关键字

1

2

3

4

5

6

7

8

class Chinse(object):

    country = 'China'

    def __init__(self, name, age):

        self.name = name

        self.age = age

    def talk(self):

        print('%s is talking' % self.name)

  

方式二:就是手动模拟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

#准备工作:

#创建类主要分为三部分

  1 类名

  2 类的父类

  3 类体

# 类名

class_name = 'Chinese'

# 类的父亲

class_bases = (object, )

# 类体

class_body = """

country = 'China'

def __init__(self, home, age):

    self.name = name

    self.age = age

     

def talk(self):

    print('%s is talking' % self.name)

    """

  

  步骤一(先处理类体->名称空间):类体定义的名字都会存放于类的名称空间中(一个局部的名称空间),我们可以事先定义一个空字典,然后用exec去执行类体的代码(exec产生名称空间的过程与真正的class过程类型,只是后者会将__开头的属性变形),生成类的局部名称空间,即填充字典

1

2

3

4

class_dic = {}

exec(class_body, globals(), class_dic)

print(class_dic)

  

步骤二:调用元类type(也可以自定义)来产生类Chinense

1

2

3

4

5

6

Foo = type(class_name, class_bases, class_dic)  # 实例化type,得到对象Foo,即我们用class定义的类Foo

print(Foo)

print(type(Foo))

print(isinstance(Foo, type))

  

我们看到,type接收三个参数:

  • 第1个参数时字符串‘Foo’, 表示类名
  • 第2个参数时元组(object,)表示所有的父类
  • 第3个参数时字典,这里是一个空字典,表示没有定义属性和方法

补充:若Foo类有继承,即class Foo(Bar)。。。则等同于type(‘Foo‘,(Bar,),{})

五 自定义元类控制类的行为

1

# 一个类没有声明自己的元类,默认他的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类(顺便也可以瞅瞅元类如何控制类的行为,工作流程是什么)

  5步带你学会元类:

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

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

# 步骤一:如果People=type(类名,类的父类们,类的名称空间),那么我们定义元类如下,来控制类的创建

class Mymeta(type):  # 继承默认元类的一堆属性

    def __init__(self, name, class_bases,class_dic):

        if '__doc__' not in class_dic or not class_dic.get('__doc__').strip():

            raise TypeError('必须为类指定文档注释')

        if not class_name.istitle():

            raise TypeError('类名首字母必须大写')

        super(Mymeta, self).__init__(class_name, class_bases,class_dic)

    class People(object, metaclass=Mymeta):

        country = 'China'

        def __init__(self, name, age):

            self.name = name

            self.age = age

        def talk(self):

            print('%s is talking' % self.name)

    # 步骤二:如果我们想控制类实例化的行为,那么需要先储备知识__call__方法的使用

class People(object, metaclass=type):

        # country = 'China'

    def __init__(self, name, age):

        self.name = name

        self.age = age

    def __call__(self*args, **kwargs):

        print(self, args, kwargs)

    def talk(self):

        print('%s is talking' % self.name)

#

# # 调用People,并不会触发__call__

obj = People('egon'18)

#

# # 调用对象obj(1,2,3,a=1,b=2,c=3),才会触发对象的绑定方法obj.__call__(1,2,3,a=1,b=2,c=3)

obj(123, a=1, b=2, c=3)     # 打印:<__main__.People object at 0x0000000009CCD3C8> (1, 2, 3) {'a': 1, 'b': 2, 'c': 3}

# 总结:如果说类People是元类type的实例,那么在元类type内肯定也有一个__call__,会在调用People('egon',18)时触发执行,然后返回一个初始化好了的对象obj

# 步骤三:自定义元类,控制类的调用(即实例化)的过程

class Mymeta(type):  # 继承默认元类的一堆属性

    def __init__(self, class_name, class_bases, class_dic):

        if not class_name.istitle():

            raise TypeError('类名首字母必须大写')

        super(Mymeta, self).__init__(class_name, class_bases, class_dic)

    def __call__(self*args, **kwargs):

        print(self, args,kwargs)    # <class '__main__.People'> ('egon', 18) {}

        # 1.实例化People,产生空对象obj

        obj = object.__new__(self)

        # 2、调用People下的函数__init__,初始化obj

        self.__init__(obj, *args, **kwargs)

        #3、返回初始化好了的obj

        return obj

class People(object, metaclass=Mymeta):

    country = 'China'

    def __init__(self, name, age):

        self.name = name

        self.age = age

    def talk(self):

        print('%s is talking' % self.name)

obj = People('egon'18)

print(obj.__dict__) # {'name':'egon', 'age':18}

# 步骤四:

class Mymeta(type):     # 继承默认元类的一堆属性

    def __init__(self, name, class_bases,class_dic):

        if not class_name.istitle():

            raise TypeError('类名首字母必须大写')

        super(Mymeta, self).__init__(class_name, class_bases, class_dic)

    def __call__(self*args, **kwargs):

        print(self, args, kwargs)

        # 1、调用self,即PeoPle下的函数__new__,在该函数内完成:1、产生空对象obj,2、初始化 3、返回obj

        obj = self.__new__(self*args, **kwargs)

        # 2、一定记得返回obj,因为实例化People(....)取得就是__call__的返回值

        return obj

class People(object,metaclass=Mymeta):

    country = 'china'

    def __init__(self, name, age):

        self.name = name

        self.age = age

    def talk(self):

        print('%s is talking' % self.name)

    def __new__(cls*args, **kwargs):

        obj = object.__new__(cls)

        cls.__init__(obj, *args, **kwargs)

        return obj

obj = People('egon'18)

print(obj.__dict__)

# 步骤五:基于元类实现单例模式,比如数据库对象,实例化时参数都一样,就没有必要重复产生对象,浪费内存

class Mysql:

    __instance = None

    def __init__(self, host = '127.0.0.1', port = '3306'):

        self.host = host

        self.port = port

    @classmethod

    def singleton(cls*args, **kwargs):

        if not cls.__instance:

            cls.__instance=cls(*args, **kwargs)

        return cls.__instance

obj1 = Mysql()

obj2 = Mysql()

print(obj1 is obj2) # False

obj3 = Mysql.singleton()

obj4 = Mysql.singleton()

print(obj3 is obj4) # True

# 应用:定制元类实现单例模式

class Mymeta(type):

    def __init__(self, name, bases, dic):   # 定义类Mysql时就触发

        self.__instance = None

        super().__init__(name, bases, dic)

    def __call__(self*args, **kwargs):    # Mysql(....)时触发

        if not self.__instance:

            self.__instance = object.__new__(self)  # 产生对象

            self.__init__(self.__instance, *args, **kwargs) # 初始化对象

            # 上述两步可以合成下面一步

            # self.__instance = super().__call__(*args, **kwargs)

        return self.__instance

class Mysql(metaclass=Mymeta):

    def __init__(self, host = '127.0.0.1', port='3306'):

        self.host = host

        self.port = port

obj1 = Mysql()

obj2 = Mysql()

print(obj1 is obj2)

  

六 练习题

练习题一:在元类中控制把自定义类的数据属性都变成大写

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

# 练习题一:在元类中控制把自定义类的数据属性都变成大写

class Mymetaclass(type):

    def __new__(cls, name, bases, attrs):

        update_attrs = {}

        for k, v in attrs.items():

            if not callable(v) and not k.startswith('__'):

                update_attrs[k.upper()] = v

            else:

                update_attrs[k] = v

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

class Chinese(metaclass=Mymetaclass):

    country = 'China'

    tag = 'Legend of the Dragon'    # 龙的传人

    def walk(self):

        print('%s is walking' % self.name)

print(Chinese.__dict__)

  

练习题二:在元类中控制自定义的类无需init方法

1.元类帮其完成创建对象,以及初始化操作

2.要求实例化时传参必须为关键字形式,否则抛出异常TypeError: must use keyword argument

3.key作为用户自定义类产生对象的属性,且所有属性变成大写

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

class Mymetaclass(type):

    # def __new__(cls, name, bases, attrs):

    #     update_attrs = {}

    #     for k, v in attrs.items():

    #         if not callable(v) and not k.startswith('__'):

    #             update_attrs[k.upper()] = v

    #         else:

    #             update_attrs[k] = v

    #     return type.__new__(cls, name, bases, update_attrs)

    def __call__(self*args, **kwargs):

        if args:

            raise TypeError('must use keyword argument for key function')

        obj = object.__new__(self)  # 创建对象,self为类Foo

        for k, v in kwargs.items():

            obj.__dict__[k.upper()] = v

        return obj

class Chinese(metaclass=Mymetaclass):

    country = 'China'

    tag = 'Legend of the Dragon'    # 龙的传人

    def walk(self):

        print('%s is walking' % self.name)

= Chinese(name = 'egon', age=18, sex='male')

print(p.__dict__)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值