python之路---面向对象之元类

什么是元类

在python中我们通过关键字class来定义/创建一个类

class People:
    def __init__(self, name, age):
        self.name = name
        self.age = age


obj1 = People('bob', 32)

我们通过调用(实例化)类People产生了obj1这个对象,而python中有一切皆对象的思想,那么类People本身也是对象,它应该也是通过People = 某个类()的调用而产生的,这个类就是元类

可以将元类理解为: 元类是类的类,我们定义类的过程就是元类的实例化过程

那么class关键字是如何创建类的?

如果说我们自定义类这个对象是通过元类而产生的,也就是执行了类对象 = 元类(...)的过程,而括号内应该传入的参数是一个类的关键组成部分

1 传入类名. 2 传入基类 3 传入类的名称空间 最终调用type()这个类进行实例化

我们可以模拟class关键字产生类的步骤:

class_name = 'People'
class_bases = (object,)
class_dic = {}
class_body = '''     
def __init__(self, name, age):    
    self.name = name
    self.age = age
'''  # 类在定义阶段就会执行类体代码,将产生的名字存放到类的名称空间中
exec(class_body, {}, class_dic) #这一步是模拟类体代码的执行过程:产生名称空间,并存放执行过程中产生的名字... 
People = type(class_name,class_bases,class_dic)  #实例化type类
obj1 = People('bob',32) #实例化People类

print(People__dict__)   
print(obj1.__dict__) 
#可以发现与通过class产生的类无差

为什么要了解使用class关键字创造类的过程?

可以自定义元类来控制类的产生与类的调用过程

1.控制类的产生过程:

   我们知道产生对象实际上是发生两件事:创建一个空对象和自动触发构造方法__init__,将空对象和括号内的参数传入

   so,也就是在我们创建类的时候是触发了元类的__init__方法,并将类的名字,基类,类的名称空间传入,

   我们就可以在__init__方法内加上逻辑判断控制类的产生过程

class MyError(BaseException):  # 自定义异常类
    def __init__(self, msg):
        super().__init__()
        self.msg = msg

    def __str__(self):
        return '%s' % self.msg


class MyType(type):  # 一定要继承type类,否则就是在定义普通的类
    def __init__(self, class_name, class_bases, class_dic):
        super().__init__(class_name, class_bases, class_dic)  # 这里我们重写了父类type类的方法

        if not class_name.istitle():
            raise MyError('操作错误:类名第一个字母必须为大写!')

        doc = class_dic.get('__doc__')
        if doc is None or len(doc) == 0:
            raise MyError('类体代码错误:必须设置类的注释文档并且注释文档不能为空!')


class People(object, metaclass=MyType):
    '''
    通过自定义元类控制类的产生过程
    '''
    # 不指定,默认通过type()类,实际上就是People = MyType(class_name,class_bases,class_dic)的过程
    pass


#如果我们将类名改为people小写:
__main__.MyError: 操作错误:类名第一个字母必须为大写
#如果我们不设置文档或者文档为空:
__main__.MyError: 类体代码错误:必须设置类的注释文档并且注释文档不能为空!

2.控制类的调用过程(控制对象的产生):

   1.我们知道一个对象的调用是触发了其类的__call__方法(一个对象的可调用,说明其类中必然拥有__call__方法(当然也可能在类的父类中))

class People:
    def __init__(self,name):
        self.name = name


bob = People('bob')
bob()   #错误,原因bob这个对象的类以及People的父类object内都没有__call__方法
class Bar:
    def __call__(self, *args, **kwargs):
        pass


class People(Bar):
    def __init__(self,name):
        self.name = name

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


bob = People('bob')
bob() #不会报错

 2.我们产生对象就是调用了自定义的那个类(本身也是对象),而调用自定义这个类对象,就是触发了其类的__call__方法

而对于产生一个对象又发生了三件事:

    1.产生一个空对象

    2.自动触发__init__构造方法,并将空对象以及括号内的参数传入

    3 返回这个对象

所以我们在__call__内也应该做这三件事:

class MyType(type):  # 一定要继承type类,否则就是在定义普通的类
    def __init__(self, class_name, class_bases, class_dic):
        super().__init__(class_name, class_bases, class_dic)  # 这里我们重写了父类type类的方法

    def __call__(self, *args, **kwargs):  # 这里的self为People这个类
        # 创建一个空对象
        obj = self.__new__(self)  # 必须传参,代表创建一个People的空对象

        # 触发构造方法
        self.__init__(obj, *args, **kwargs)

        # 我们可以在这里加上其它逻辑,以控制类的实例化过程(这里将对象的数据属性进行封装)
        obj.__dict__ = {'_%s__%s' % (self.__name__, key): value for key, value in obj.__dict__.items()}

        # 返回对象
        return obj


class People(object, metaclass=MyType):
    def __init__(self, name, age):
        self.name = name
        self.age = age


bob = People('bob', 32)
print(bob.__dict__)

这里需要注意的是,上面的self.__new__,按照属性查找来说,是来自于父类object(而非自定义元类Mytype,或者type类),

那么为什么不直接使用object.__new__,原因是,使用object,就是指名道姓的去调用一个类的函数,它忽略了属性的查找顺序,如果类本身重写了__new__,还是去继承object类(方法重写就变得无意义)

再看属性查找:

我们之前说对象的属性查找顺序是:对象自己--->对象的类--->按照MRO表去查询对象类的父类

而,python中说一切皆对象,那么类本身也是对象,对于类这个对象而言,它的属性查找顺序是?

class Mythod(type):
    x = 'from metaclass'

    def __init__(self, class_name, class_bases, claa_dic):
        super(Mythod, self).__init__(class_name, class_bases, claa_dic)


class Foo:
    x = 'from foo'


class Bar(Foo):
    x = 'from bar'


class People(Bar, object, metaclass=Mythod):
    x = 'from People'

    def __init__(self, name, age):
        self.name = name
        self.age = age


print(People.__mro__)
print(People.x)

#MRO表:(<class '__main__.People'>, <class '__main__.Boo'>, <class '__main__.Foo'>, <class 'object'>)

#运行,发现 x = 'from People' ,将People内的x注释
#运行,发现 x = 'from bar' ,将Bar内的x注释
#运行,发现 x = 'from foo',将Foo内的x注释
#运行,发现 x = 'from metaclass'

小结:对于类这个对象,查找属性是先遵循MRO列表查询,如果查询不到,再到元类层去查找(这里的元类层包含:自定义的元类Mythod---->type类)

猜你喜欢

转载自blog.csdn.net/ltfdsy/article/details/82111604
今日推荐