面向对象之元类

1. 元类

1.1 什么是元类

  • 在python中,一切皆对象,那么我们用class关键字定义的类也是一个对象,而负责产生该对象的类称之为元类

1.2 为什么用元类

  • 元类是负责产生类的,所以我们可以通过元类控制类的产生过程,把一些不符合的筛选掉,同时还可以通过元类里的__call__方法控制对象的产生

1.3 类的创建

1.3.1 正常定义一个类

# 定义一个类
class Foo(object):  # python3中默认继承object类
    count = 0
    
    def __init__(self,name,age):
        self.name = name
        self.age = age
        
    @property    
    def func(self):
        print('from func')
        
f1 = Foo('hades',27)
print(f1.__dict__)
f1.func
{'name': 'hades', 'age': 27}
from func

1.3.2 内置函数exec

exec会把字符串按照python语法解析成代码,把解析完的代码放入字典中

  • 语法:exec(str:-->符合Python语法规范的字符串, {}:-->Python中的内置函数, 变量名:-->dict)
s =  '''
count = 0
    
def __init__(self,name,age):
    self.name = name
    self.age = age

def func(self):
    print('from func')
'''
dic = dict()
exec(s,{},dic)  
print(dic)
{'count': 0, '__init__': <function __init__ at 0x0000023CB5544C80>, 'func': <function func at 0x0000023CB55FD378>}

1.3.3 type创建类

1.3.3.1 类的三大组成

  1. 类名
  2. 基类
  3. 类的名称空间
# 1. 类名
class_name = 'Foo'

# 2. 类体代码(需要按照python语法规则)
class_body = '''
count = 0
    
def __init__(self,name,age):
    self.name = name
    self.age = age

@property
def func(self):
    print('from func')
'''

# 3. 基类(父类)
class_bases =(object,)  # 必须加逗号,形成一个元组
class_dict = dict()
exec(class_body, {}, class_dict)   # 产生类体名称空间
print(class_dict)
{'count': 0, '__init__': <function __init__ at 0x0000023CB56842F0>, 'func': <property object at 0x0000023CB56773B8>}

1.3.3.2 type关键字生成类

  • 语法:type(类名:-->str,基类:-->tuple, 类的名称空间:-->dict)
Foo1 = type(class_name, class_bases, class_dict)
print(Foo1)
<class '__main__.Foo'>
f2 = Foo1('hades', 27)
print(f2.__dict__)
f2.func
{'name': 'hades', 'age': 27}
from func

这样我们就生成了一个类

1.3.4 自定义元类控制类的创建

  • 使用自定义个元类
class Mymeta(type):  # 只有继承了type类才能称之为一个元类,否则就是一个很普通的类
    def __init__(self, class_name, class_bases, class_dict):
        print(class_name)
        print(class_bases)
        print(class_dict)
        super().__init__( class_name, class_bases, class_dict)  # 使用父类功能生成一个类对象
        print('创建类成功了')
  • 分析用class自定义类的运行原理(而非元类的的运行原理):

    1.拿到一个字符串格式的类名class_name = 'Foo'

    2.拿到一个类的基类们class_bases = (obejct,)

    3.执行类体代码,拿到一个类的名称空间class_dict={...}

    4.调用Foo1 = type(class_name, class_bases, class_dict)

class Foo(metaclass=Mymeta):  # python3中默认继承object类
    count = 0
    
    def __init__(self,name,age):
        self.name = name
        self.age = age
        
    @property    
    def func(self):
        print(f'name "{self.name}" from func')
Foo
()
{'__module__': '__main__', '__qualname__': 'Foo', 'count': 0, '__init__': <function Foo.__init__ at 0x0000023CB5684D08>, 'func': <property object at 0x0000023CB5690368>}
创建类成功了
f1 = Foo('Bonnie',16)
f1.func
name "Bonnie" from func

1.4 应用

  • 自定义元类控制类的产生过程,类的产生过程其实就是元类的调用过程

  • 我们可以控制类必须有文档,类名首字母要大写,可以使用如下的方式实现

class Mymeta(type): 
    def __init__(self, class_name, class_bases, class_dict):
        if not class_name.istitle():
            raise TypeError('类名首字母要进行大写')
            
        if (not class_dict.get('__doc__')) or len(class_dict['__doc__']) == 0:
            raise TypeError("必须有文档注释")
            
        super().__init__( class_name, class_bases, class_dict)
        
# 类名首字母没有大写
try:
    class foo(metaclass=Mymeta):
        def __init__(self,name,age):
            self.name = name
            self.age = age
            
except Exception as e:
    print(e)
类名首字母要进行大写
# 没有注释
try:
    class Foo(metaclass=Mymeta):
        def __init__(self,name,age):
            self.name = name
            self.age = age
            
except Exception as e:
    print(e)
必须有文档注释
try:
    class Foo(metaclass=Mymeta):
        """测试文件"""
        def __init__(self,name,age):
            self.name = name
            self.age = age
            
    f1 = Foo('hades',20)
    print(f1.__dict__)   # {'name': 'hades', 'age': 20}
            
except Exception as e:
    print(e)
{'name': 'hades', 'age': 20}

1.5 自定义元类控制类的实例化

可以通过__call__方法进行实现

class Mymeta(type): 
    def __init__(self, class_name, class_bases, class_dict):
        if not class_name.istitle():
            raise TypeError('类名首字母要进行大写')
            
        if (not class_dict.get('__doc__')) or len(class_dict['__doc__']) == 0:
            raise TypeError("必须有文档注释")
            
        super().__init__( class_name, class_bases, class_dict)
        
    def __call__(self,*args,**kwargs):
        print(self)
        print(args)
        print(kwargs)
        
        obj = self.__new__(self)
        self.__init__(obj,*args,**kwargs)
        print(obj)
        return obj

class Foo(metaclass=Mymeta):
    """测试文件"""
    def __init__(self,name,age):
        self.name = name
        self.age = age
        
f1 = Foo('Bonnie',18)
print(f1)
print(f1.__dict__)
<class '__main__.Foo'>
('Bonnie', 18)
{}
<__main__.Foo object at 0x0000023CB56B4048>
<__main__.Foo object at 0x0000023CB56B4048>
{'name': 'Bonnie', 'age': 18}
  • 类的调用,即类实例化就是元类的调用过程,可以通过元类Mymeta的__call__方法控制

  • 分析:调用Pepole的目的

    1. 先造出一个People的空对象

    2. 为该对空对象初始化独有的属性

    3. 返回一个初始化好的对象

1.6 自定制元类后类的继承顺序

  • 先对象本身-->类-->父类-->父类的父类-->object-->自己定制的元类-->type
posted @ 2019-06-22 11:56  蔚蓝的爱  阅读(221)  评论(0编辑  收藏  举报