欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

面向对象之元类(metaclass)

程序员文章站 2022-04-28 14:15:04
一、前言: 要搞懂元类必须要搞清楚下面几件事: 类创建的时候,内部过程是什么样的,也就是我们定义类class 类名()的过程底层都干了些啥 类的调用即类的实例化过程的了解与分析 我们已经知道元类存在的情况下的属性查找新顺序分析 类创建的时候,内部过程是什么样的,也就是我们定义类class 类名()的 ......

一、前言:

要搞懂元类必须要搞清楚下面几件事:

  • 类创建的时候,内部过程是什么样的,也就是我们定义类class 类名()的过程底层都干了些啥

  • 类的调用即类的实例化过程的了解与分析

  • 我们已经知道元类存在的情况下的属性查找新顺序分析

-------------------------------------------------------------------------------------------------------------------------------------------------

1、先来认识认识类的创建过程:

class newclass():   # class定义一个类,类名为:newclass
# 下面的代码都是类体代码,也就是类的属性们 def __init__(self, name, age): self.name = name self.age = age coutry = 'china' def task(self): print('%s is sleeping' % self.name)

  当我们用class声明要创建一个类的时候,实际上内部流程是这样的(以上面的为例):

  ①.定义类名newclass:class_name = 'newcalss'

  ②.设定这个类newclass的父类(基类)们(一个类可以继承多个类):class_bases = (object,) 不设定默认继承object

  ③.执行类体代码,拿到类的名称空间:class_dic = {...} (这里的字典就是我们前面学习类的时候查看类里面的属性方法.__dict__的内容。

2、我们调用创建的类newclass(也就是实例化对象)的过程:

t1 = newclass('sgt', 30)  # 调用类,实例化t1这个对象

通过调用类newclass实例化出对象t1时,会发生以下三件事:

  ①.先产生一个空的对象obj

  ②.调用__init__方法,对对象obj进行初始化,将默认属性丢进空对象对应的名称空间中

  ③.将初始化的对象obj返回,t1,就是返回结果的接受者,也就是说t1就是实例化出来的一个对象

二、开始认识元类

1、类产生的过程分析:

  还是先定义一个类来分析分析:

class newclass():  
    def __init__(self, name, age):
        self.name = name
        self.age = age
coutry = 'china'
def task(self): print('%s is sleeping' % self.name)
t1 = newclass('sgt', 30) 

  首先,所有的对象都是实例化或者说调用类而得到的(调用类的过程称之为实例化对象),比如对象t1是调用类newclass得到的

  如果一切皆对象,那么newclass本质也可以看成一个对象,既然所有的对象都是调用类得到的,那么是不是可以大胆的想象我们声明class创建一个类newclass的时候,是否也是调用一个更高级的类得到的呢?

  事实就是我们想的那样,这个‘实例化类的类’就是我们今需要好好了解的‘元类’。

  于是我们可以大致有这么过程:产生newclass的过程一定发生了:newclass = 元类(...)

# 我们来分别打印一下实例化出的对象t1和创建的类newclass的类型:
print(type(t1))
print(type(newclass))
# 结果是:
# <class '__main__.newclass'>   
# <class 'type'>      
# 我们可以推出:                   
# t1是调用newclass产生的
# newclass是调用type产生的

面向对象之元类(metaclass)

  一开始我们就提前了解了class出一个类newclass时候发生的过程,这里再补充一下,如果我们不用class创建类的另外一种原始方法:

# 引入函数exec(object, globals, locals)
exec用法:
object:类体代码,包含一系列python代码的字符串
globals:全局作用域(字典形式),如果不指定,默认为globals()
locals:局部名称空间(类的名称空间)
可以把exec命令的执行当成是一个函数的执行,会将执行期间产生的名字存放于局部名称空间中

# 不依赖class关键字创建一个自定义类
类名class_name= 'newcalss'
继承的类们:class_bases = (object,)
类体代码:class_body = '''
def __init__(self, name, age):
    self.name = name
    self.age = age
coutry = 'china'
def task(self):
    print('%s is sleeping' % self.name)

''' exec(class_body,{},class_dic) 

# 创建一个名称空间,将类体代码放入class_dict中,这个class_dict就代表exec创建的名称空间 
# 调用type得到自定义的类: 
newclass = type(class_name, class_bases, class_dict)

2、自定义元类,控制类newclass的创建过程

  既然知道了类的创建过程,那么我们就可以自定义元类来控制类的创建

  首先:一个元类如果没有声明自己的元类,默认它的元类就是type,出了使用内置元类type,我们也可以通过集成type来自定义元类,然后使用metaclass关键字参数为一个类指定元类

class mymeta(type):  # 只有继承了type的类才能称之为一个元类,否则就是普通的类
    pass

class newclass(object,metaclass=mymeta):  # 继承基类object,为newclass指定元类为mymeta
    def __init__(self, name, age):
        self.name = name
        self.age = age

    coutry = 'china'

    def task(self):
        print('%s is sleeping' % self.name)

  然后:自定义元类可以控制类的产生过程,类的产生过程其实就是元类的调用过程,即:

  newclass = mymeta(‘newclass’,(object,),{.....}),

  一开始我们先预习了,调用类是发生的过程:同理调用mymeta会先产生一个空对象newclass,然后连同mymeta括号内的参数一同传给mymeta下的__init__方法,完成初始化,所以我们可以在这个过程中做一下事情:

class mymeta(type):  # 只有继承了type的类才能称之为一个元类,否则就是普通的类
    def __init__(self, class_name, class_bases, class_dict):
        super().__init__(class_name, class_bases, class_dict)

        if class_name.islower():    #  给类名做限制,必须为驼峰体,否则抛异常
            raise typeerror('类名必须为驼峰体')
        # 给类中注释的存在性加以限制,必须要有注释且不能为空,否则抛异常
        if '__doc__' not in class_dict or len(class_dict['__doc__'].strip(' \n')) == 0:
            raise typeerror('类必须要求有文档注释,且不能为空')

class newclass(object,metaclass=mymeta):  # 继承基类object,为newclass指定元类为mymeta
    '''
    这是newclass类的注释
    '''
    def __init__(self, name, age):
        self.name = name
        self.age = age

    coutry = 'china'

    def task(self):
        print('%s is sleeping' % self.name)

3、自定义元类,控制类newclass的调用过程

  一开始提过:调用类就是实例化对象,那么调用这个行为,就必须要知道__call__这个知识点,所以先来说说__call__:

class newclass():
    coutry = 'china'
    def task(self):
        print('%s is sleeping' % self.name)

    def __call__(self, *args, **kwargs):
        print('__call__被调用了>>>', self)
        print('__call__被调用了>>>', args)
        print('__call__被调用了>>>', kwargs)
t1 = newclass()

## 要想让t1这个对象可调用,需要在该对象的类中定义一个__call__方法,
#       该方法会在对象t1在调用时候自动触发
## 调用t1的返回值就是__call__方法的返回值
res = t1('a', 'b', 8, name = 'jason', age = 18)

# 右键运行结果:
__call__被调用了>>> <__main__.newclass object at 0x000001ad69359828>  
__call__被调用了>>> ('a', 'b', 8)
__call__被调用了>>> {'name': 'jason', 'age': 18}

  由上面的例子得知,调用一个对象,就会触发对象所在类中的__call__方法的执行,所以我们在调用类newclass(这里将类也可以看成对象)实例化对象时候,也应该在类newclass的元类中必然存在一个__call__方法。

class mymeta(type):
    def __init__(self, class_name, class_bases, class_dict):
        super().__init__(class_name, class_bases, class_dict)

    def __call__(self, *args, **kwargs):
        print(self)    # <class '__main__.newclass'>
        print(args)    # ('sgt', 18)
        print(kwargs)  # {}
        return 123

class newclass(object,metaclass=mymeta):
    def __init__(self, name, age):
        self.name = name
        self.age = age
    coutry = 'china'
    def task(self):
        print('%s is sleeping' % self.name)

t1 = newclass('sgt', 18)
print(t1)  # 123

  通过上面例子可以总结出:

  • 调用newclass就是在调用newclass类中的__call__方法,(newclass类中没有按照属性查找去基类中找)
  • 触发__call__方法后会将newclass传给self,溢出的位置参数传给*,溢出的关键字参数传给**
  • 调用newclass的返回值就是触发__call__方法函数的返回值,这里通过打印t1得出结果123可以得出。

   好了,我们在来回顾下,实例化对象t1的过程: 

  1. 先产生一个空的对象obj
  2. 调用__init__方法,对对象obj进行初始化,将默认属性丢进空对象对应的名称空间中
  3. 将初始化的对象obj返回,t1,就是返回结果的接受者,也就是说t1就是实例化出来的一个对象

  所以,对应的newclass在实例化出对象t1时候也应该做上面三件事:

class mymeta(type):
    def __init__(self, class_name, class_bases, class_dict):
        super().__init__(class_name, class_bases, class_dict)

    def __call__(self, *args, **kwargs):
        # 1 调用__new__产生一个空对象obj:
        obj = self.__new__(self)  # __new__会产生空对象的名称空间
        # 这里self.__new__是通过self(类newclass)来调用__new__方法,通过属性查找默认在基类object中
        # 括号里self的意思是创建类newclass的对象的名称空间。
        # 2 调用__init__初始化空对象obj
        self.__init__(obj, *args, **kwargs) # 注意这里的第一个参数是obj,因为我们初始化的是我么创建的空对象
        # 3 返回初始化好的对象obj
        return obj
class newclass(object,metaclass=mymeta):
    def __init__(self, name, age):
        self.name = name
        self.age = age
    coutry = 'china'
    def task(self):
        print('%s is sleeping' % self.name)

t1 = newclass('sgt', 18)   # 实例化对象
print(t1.__dict__)  # {'name': 'sgt', 'age': 18}  # 查看实例化对象的结果

  上面就是我么通过调用类,实例化对象的过程元类中的__call__做的事情,既然知道了这个过程,我么也能自定义私人的元类,那么我们就可以从基础上改写__call__方法来控制newclass类调用的过程,比如将newclass类实例化的对象的属性变成我们想要的结果。

class mymeta(type):
    def __init__(self, class_name, class_bases, class_dict):
        super().__init__(class_name, class_bases, class_dict)

    def __call__(self, *args, **kwargs):
        obj = self.__new__(self)
        self.__init__(obj, *args, **kwargs)  # 在初始化完的对象返回之前进行修改
        res = {k.upper(): v for k, v in obj.__dict__.items()}  # 将对象的属性名大写
        return res

class newclass(object,metaclass=mymeta):
    def __init__(self, name, age):
        self.name = name
        self.age = age
    coutry = 'china'
    def task(self):
        print('%s is sleeping' % self.name)

t1 = newclass('sgt', 18)
print(t1)  # {'name': 'sgt', 'age': 18}

4、在知道元类存在的情况下的属性查找顺序

   前面我们在没有学习元类的时候对属性查找的顺序的认识只终止于object,但是今天我们知道了元类的存在那么此时属性查找顺序是什么样的呢?

class mymeta(type):  
    n = 444
    def __call__(self, *args, **kwargs): 
        obj = self.__new__(self)
        self.__init__(obj, *args, **kwargs)
        return obj

class bar(object):
    n = 333

class foo(bar):
    n = 222

class newclass(foo,metaclass=mymeta):
    def __init__(self, name, age):
        self.name = name
        self.age = age
    n = 111
    coutry = 'china'
    def task(self):
        print('%s is sleeping' % self.name)

print(newclass.n)
# 自下而上依次注释各个类中的n=xxx,然后重新运行程序,
# 发现n的查找顺序为newclass->foo->bar->object->mymeta->type

  此时,属性查找顺序应该两层,一层是对象层(按照mro顺序查找),第二层类层(元类中查找)

面向对象之元类(metaclass)

# 查找顺序:
# 1、先对象层:newclass->foo->bar->object
# 2、然后元类层:mymeta->type