Python学习笔记 元类(metaclass)

本文转自:https://www.cnblogs.com/linhaifeng/articles/8029564.html
少量修改

目录
  什么是元类
  class关键字创建类的的流程分析
  自定义元类控制类的创建
  自定义元类控制类的调用
  再看属性查找
什么是元类

  一切源自于一句话:Python中一切皆为对象。让我们先来定义一个类,然后逐步分析。

class MyText(object):
    work = '刺客'
    
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    def say(self):
        print('my name is {}'.format(self.name))

  所有的对象都是实例化(调用类的过程称为类的实例化)得到的,比如对象 sample 是调用类 MyText 得到的。

sample = MyText('阿珍', 18)
print(type(sample)) # 查看对象sample的类是<class '__main__.MyText'>

  如果一切皆为对象,那么类 MyText 的本质也是一个对象,既然所有对象都是调用类得到的,那么 MyText 必然也是调用了一个类得到的,这个类就称为元类。
  于是我们可以推导出===> 产生 MyText 的过程一定发生 MyText = 元类(…)

print(type(MyText))
# 结果为<class 'type'>,证明是调用了type这个元类而产生了MyText,即默认的元类为type

在这里插入图片描述

class 关键字创建类的流程分析

  上文我们基于 Python 中一切皆为对象的概念分析出:我们用class关键字定义的类本身也是一个对象,负责产生该对象的类称之为元类(元类即是类的类,是女娲),内置的元类为 type。
  class关键字再帮我们创建类时,必然帮我们调用了元类 MyText = type(...),那调用 type 时传入的参数是什么呢?必然是类的关键组成部分,而一个类有三大组成部分,分别是:

  1. 类名; class_name = 'MyText'
  2. 类的基类元组; class_bases = (object,)
  3. 类的命名空间;类的命名空间是执行类体代码而得到

 调用 type 时会依次传入以上三个参数
 综上,class关键字创建一个类的过程应该细分为以下四个步骤:
在这里插入图片描述
 补充:exec() 函数的用法:

'''
    exec(__object, __globals, __locals)
    功能:执行字符串__object中的代码,并将产生的名字存放在局部命名空间中
    参数:
        __object:包含代码的字符串
        __globals:全局作用域(字典形式, 需包含__object中用到的全局变量), 若不指定,则为 globals()
        __locals:局部作用域(字典形式, 用于存放__object中产生的名字),若不指定,则为 locals()
'''
_globals = {'num': 10}
_locals = {}
string = '''
sun = num + 10
'''
exec(string, _globals, _locals)
print(_globals, _locals, sep='\n')
# {'num': 10, ...}
# {'sum': 20}
自定义元类控制类 MyText 的创建

  一个类没有生命自己的元类,则默认它的元类为 type,除了使用内置元类,我们也可以通过继承 type 来自定义元类,然后使用 mateclass 关键字参数为一个类指定元类。

# 例如下
class Mymeta(type): 
#只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
    pass


class MyText(object,metaclass=Mymeta):
# 同于:MyText=Mymeta('OldboyTeacher',(object),{...})
    work = '刺客'
    
    def __init__(self,name,age):
        self.name=name
        self.age=age
        
    def say(self):
        print('my name is {}'.format(self.name))

  自定义元类可以控制类的生产过程(就像类可以控制实例化对象的生产过程),类的生产过程其实就是元类的调用过程,即MyText = Mymeta('MyText', (boject,),{...})调用 Mymeta 会先产生一个空对象 MyText,然后连同调用 Mymeta 括号内的参数一同传给 Mymeta 下的 __init __ 方法,完成类的初始化,于是乎:

# 例如下
class MyMeta(type):
    # 只有继承了type类的类才是一个元类,
    
    def __init__(self, class_name, class_bases, class_dic):
        # 以上四个参数都是自动传入
        # print(self) # 可以看看这里会输出什么
        # print('class_name:', class_name, '\n', 'class_bases:', class_bases, '\n', 'class_dic:', class_dic, sep='')
        super().__init__(class_name, class_bases, class_dic)
        # 调用父类的构造方法创建类
        if class_name.islower():
            raise TypeError('类名{}请修改为驼峰体'.format(class_name))
        if '__doc__' not in class_dic or \
                len(class_dic['__doc__'].strip(' \n')) == 0:
            raise TypeError('类中必须有文档注释,并且文档注释不能为空')


class MyText(object, metaclass=MyMeta):
    # 等价于 MyText = MyMeta('MyText', (object,), {'__doc__': 'This is a doc', name: '阿珍'...})
    '''
    This is a doc
    '''
    work = '刺客'
    
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    def say(self):
        print('my name is {}'.format(self.name))

自定义元类控制类的调用

  众所周知,调用一个对象,就是触发对象所在类中的__call__方法(魔法方法__call__解析),如果把 MyText 也当做一个对象,那么在 MyText 这个和对象的类中也必然存在一个__call__方法。

# 例如下
class MyMeta(type):
    # 只有继承了type类的类才是一个元类
    def __call__(self, *args, **kwargs):
        print(self, args, kwargs, sep='\n')
        # <class '__main__.MyText'>
		# ('阿珍', 18)
		# {}
        return '哈哈'
class MyText(object, metaclass=MyMeta):

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

    def say(self):
        print('my name is {}'.format(self.name))

# 调用 MyText 就是在调用 MyMate 类中的__call__方法
# 然后将 MyText 传给 self ,溢出的位置参数传给*args,溢出的关键字参数传给**kwargs
# 调用 MyText 的返回值就是调用__call__的返回值        
sample = MyText('阿珍', 18)
print(type(sample)) # <class 'str'>

默认的,调用 sample = MyText('阿珍', 18)会做三件事:

  1. 产生一个空对象 sample
  2. 调用__init__方法初始化对象 sample
  3. 返回初始化好的sample

对应的,MyMeta 类中的__call__方法也用该做这三件事。

class MyMeta(type):

    def __call__(self, *args, **kwargs): # self = <class '__main__.MyText'>
        # 1、调用__new__产生一个空对象
        obj = self.__new__(self) # 此处必须传参,代表创建一个 MyText 的对象 obj
        # 2、调用__init__初始化空对象 obj
        self.__init__(obj, *args, **kwargs)
        # 3、返回初始化好的对象
        return obj

class MyText(object, metaclass=MyMeta):

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

sample = MyText('阿珍', 18)
# 同于 sample = MyMeta.__call__(MyText, '阿珍', 18)
print(sample.__dict__)
# {'name': '阿珍', 'age': 18}

现在你知道为什么说实例化一个对象最先调用的是其类的__new__和__init__方法了吧!

  上例的__call__相当于一个模板,我们可以在该基础上改写__call__的逻辑,从而控制调用 MyText 的过程,比如将 MyText 的对象的所有属性都变成私有的。

# 例如下
class MyMeta(type):

    def __call__(self, *args, **kwargs): # self = <class '__main__.MyText'>
        # 1、调用__new__产生一个空对象
        obj = self.__new__(self) # 此处必须传参,代表创建一个 MyText 的对象 obj
        # 2、调用__init__初始化空对象 obj
        self.__init__(obj, *args, **kwargs)
        # 利用字典生成式来使属性私有化
        obj.__dict__ = {'_{}__{}'.format(self.__name__, k): v for k, v in obj.__dict__.items()}
        # 3、返回初始化好的对象
        return obj

class MyText(object, metaclass=MyMeta):

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

sample = MyText('阿珍', 18)
print(sample.__dict__)
# {'_MyText__name': '阿珍', '_MyText__age': 18}

再看属性查找

  结合 python 继承实现原理 + 元类重新看属性的查找应该是什么样子呢?

  在学习完元类后我们了解到,其实用 class 自定义的类都是元类的实例对象(包括 object 类本身也是元类 type 的一个实例,可以用 type(object) 查看)。我们学习过继承的实现原理,如果把类当成对象去看,那么可以将下述继承说成是:对象 MyText 继承对象 Foo,对象 Foo 继承对象 Bar,对象 Bar 继承对象 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 MyText(Foo, metaclass=MyMeta):
    n = 111

    work = '刺客'

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

    def say(self):
        print('my name is {}'.format(self.name))


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

  于是属性查找应该分为两层:一层是对象层(基于 C3 算法的 MRO)的查找,一层是类层(即元类层)的查找。在这里插入图片描述

查找循序:
1、先对象层:MyText -> Foo -> Bar -> object
2、然后元类层:Mymeta -> type

依据上述总结,我们来分析一下元类 MyMeta 中__call__里的self.__new__属性的查找。

class MyMeta(type):
    n = 444

    def __call__(self, *args, **kwargs):
        # self=<class '__main__.MyText'>
        obj = self.__new__(self)
        print(self.__new__ is object.__new__) # True


class Bar(object):
    n = 333

    # def __new__(cls, *args, **kwargs):
    #     print('Bar.__new__')


class Foo(Bar):
    n = 222

    # def __new__(cls, *args, **kwargs):
    #     print('Foo.__new__')


class MyText(Foo, metaclass=MyMeta):
    n = 111

    work = '刺客'

    # def __new__(cls, *args, **kwargs):
    #     print('MyText.__new__')

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

    def say(self):
        print('my name is {}'.format(self.name))


MyText('阿珍', 18)
# 触发 MyText 的类中的__call__方法的执行,进而执行self.__new__开始查找 

  总结:MyMeta 下的__call__里的 self.__new__在 MyText、Foo、Bar里都没找到__new__属性的情况下,回去找 object 里的__new __ ,也一定会在 object 中找到一个,根本不会,也没必要再去元类 MyMeta 或 type 中查找__new __。

  我们在元类的__call__中也可以用object.__new __(self)去创建对象
在这里插入图片描述

  但我们还是推荐在__call__中使用 self.__new __(self)去创建空对象,因为这种方式会检索三个类:MyText 、Foo、Bar,而object.__new __则是直接跳过了它们三个。

  最后说明:

class MyMeta(type):
    n = 444

    def __new__(cls, *args, **kwargs):
        obj = type.__new__(cls, *args, **kwargs)
        # 必须按照这种传值方式
        print(obj.__dict__)
        # return obj # 只有在返回值是type的对象时,才会触发下面的__init__
        return '123'

    def __init__(self, class_name, class_bases, class_dic):
        print('run。。。')


class MyText(object, metaclass=MyMeta):
    n = 111

    work = '刺客'

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

    def say(self):
        print('My name is {}'.format(self.name))


print(type(MyMeta))
# 产生类MyText的过程就是在调用MyMeta,而MyMeta也是type类的一个对象,那么MyMeta之所以可以调用,一定是在元类type中有一个__call__方法
# 该方法中同样需要做至少三件事:
# class type:
#     def __call__(self, *args, **kwargs): #self=<class '__main__.MyMeta'>
#         obj=self.__new__(self,*args,**kwargs) # 产生MyMeta的一个对象
#         self.__init__(obj,*args,**kwargs) 
#         return obj

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值