python:一切皆对象

元类

如果把类也看做对象的话,那类这个对象的类就成为类的类,即元类。python中内置的默认元类是 type

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

我们用class关键字定义的所有的类以及内置的类都是由元类type实例化产生。

class机制

class是python的一个关键字,目的是用来创建类。它在底层实现类的定义本质上有四个步骤。

class People:
    def __init__(self, name):
        self.name = name
    
    def talk(self):
        print('hello')
# 类的三大特征:
	- 类名:People
	- 基类们:object,
	- 名称空间:__dict__

第一步:获取类名:class_name = People

第二步:获取基类们:class_bases = (objects, )

第三步:获取类的名称空间:class_dict = {}

class_dict = {}
class_body = """
def __init__(self, name):
    self.name = name
def talk(self):
    print('hello')
"""
exec(class_body, {}, class_dict)	
# class_dict = {'__init__': <function __init__ at 0x000002448A7B73A0>, 'talk': <function talk at 0x000002448A7B74C0>}

第四步:调用元类 type实例化产生People类这个对象

People = type(class_name, class_bases, class_dict)

上述四步是定义类的底层原理,也是我们定义产生一个类的第二种方式。

但是这种方式是复杂繁琐的,我们肯定会使用python给我们提供的class关键字来定义类。

自定义元类

上面class机制的四步中,我们可以发挥的地方在第四步,即用不同的元类实例化类这个对象,目的是为了按照需求控制类的定义和调用。

这就需要我们先定制出我们自己的元类,即自定义元类。

class People(metaclass=type):	# class机制默认的元类是type:我们可以修改metaclass参数来选择自定义元类
    def __init__(self, name):
        self.name = name
    
    def talk(self):
        print('hello')

在python中type是一切类的基石,所以我们自定义的元类也必须继承type

自定义的元类继承type的目的是为了使用type的大部分功能,我们只定制我们需要的那部分功能。

class Mymeta(type):		# 只有继承了type的类才能作为元类使用
    pass


class People(metaclass=Mymeta):	# 使用Mytema元类,即Mymeta(class_name, class_bases, class_dict)
    def __init__(self, name):
        self.name = name
    def talk(self):
        print('hello')

实例化对象的本质

我们知道实例化一个对象会发生三件事,但其实我们确切知道的只有第二件事:初始化对象(__init__的功劳)。

现在,我们详细介绍介绍这三件事是如何发生的。其实这里只谈概念,具体是实现在本文下一节讨论。

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

# 实例化的三件事:
- 创建一个空对象
- 初始化空对象
- 返回初始化完成的对象

- 创建空对象由类的__new__()方法实现,__new__创建并返回一个空对象
- __init__接收这个空对象,并初始化该对象
- 最后返回初始化完成的对象。
整个三步流程是由类的类,即元类中的__call__方法管理的。因为实例化对象,本身是类的调用。
类本身又是一个对象,当它调用的时候就会触发其类的__call__函数的执行,即实例化对象时,触发元类的__call__函数。
在元类__call__内实现实例化的三件事。

当我们默认使用的是type元类时,想要自定制实例化过程中的需求是无法实现的。因为我们无法修改内置元类type的__call__方法。
当我们使用自定义元类时,就可以实现实例化过程需求的自定制。因为我么可以重写自定义元类的__call__方法,即自定制实例化需求。

自定义元类控制类的调用

控制类的调用就是控制对象的实例化过程。类也是一个对象,它的类是元类。类的调用就会触发元类的__call__函数执行

class Mymeta(type):
    def __call__(self, *args, **kwargs):	# self是People, *args, **kwargs接收类调用时括号内的实参
        people_obj = self.__new__(self, *args, **kwargs)		# 通过类调用类下面的方法,必须手动传参self
        self.__init__(people_obj, *args, **kwargs)	# 类调用手动传参people_obj 和 *args, **kwargs,还可以是 people_obj.__init__(*args, **kwargs) 此时是对象调用,自动传参
        return people_obj


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

    def __new__(cls, *args, **kwargs):	# 此处不写__new__,在元类的__call__中就会使用其父类的__new__
        return super().__new__(cls)		# 本质还是使用父类的的__nes__,没有继承则使用object的__new__


jack = People('jack', 18)   # People.__call__('jack', 18)
print(jack.__dict__)

此处的元类Mymeta是一个自定义元类模板,我们需要自定制对象实例化过程中的需求时可以在其中的__call__里面的三步中添加需求功能。

自定义元类控制类的定义

自定义元类不仅可以控制定制实例化时的需求,还可以定制定义类时的需求。

从前面的 class机制我们知道,类定义过程的四步。我们可以通过自定义元类,实现控制class机制的第四步。

类的定义是通过元类的实例化产生的,因此类的定义过程必然牵涉到元类的__new__()、__init__()、以及元类的类的__call__()。此处,我们不讨论__call__(),原因是还没有搞清楚(太底层了)。

因此,我们通过自定义元类控制类的定义,就需要在自定义的元类中实现__new__()、__init__()两个方法。

class Mymeta(type):
    def __init__(self, class_name, class_bases, class_dict):

        if not class_name.istitle():			# 自定制需求
            raise NameError('类名首字母必须大写')

        if not self.__doc__:
            raise TypeError('必须要有文档注释')

        super().__init__(class_name, class_bases, class_dict)	# 好像不重写父类的也没问题

    def __new__(cls, *args, **kwargs):
        class_obj = type.__new__(cls, *args, **kwargs)
        # print(class_obj.__dict__)
        return class_obj



# 元类的类.__call__(self, *args, **kwargs):
# 组织管理,元类.__new__、 __init__

# People = Mymeta(class_name, class_bases, class_dict),
class People(object, metaclass=Mymeta):
    """
    deox
    """
    x = 10
    def f(self):
        pass



print(People.__dict__)

属性查找

从对象出发的属性查找:对象 --> 对象所在的类 --> 父类 --> object

从类出发的属性查找:类 --> 父类 --> object --> 元类 --> type

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄