42、Python之面向对象:元类应用于定义检查、动态注入、插件注册

引言

在上一篇文章中,我们简单聊了一下Python中元类的概念,以及如何定义一个简单的元类。虽然,我们已经学会了定义元类,但是,相信不少刚接触Python的同学,大多会产生这样的疑问:这个不太好理解的元类,费工夫学来,到底有什么用呢?

这篇文章中,我们就接着来介绍下Python中元类的应用场景。

当然,需要提前说明的是,元类的使用,属于相对高级一些的用法,正常业务场景中一般没有必要用到元类。但是,了解了元类的定义极其适用场景,在后续阅读框架源码或者进行框架功能的开发时,还是很有必要的。

类定义检查

由于元类能够控制类对象的创建,所以,如果需要对类的定义进行检查、校验,比如必须写方法的文档注释,必须实现特定方法等,则可以在元类中来实现这些逻辑。

比如,假如有这样一个场景,要用到我们的框架的,在用户自定义类时,必须基于框架提供的元类进行类的定义,同时方法的定义中必须添加文档描述。

可以通过如下代码进行模拟:

class Model(type):
    def __new__(cls, name, bases, namespace, **kwargs):
        for key, value in namespace.items():
            if key.startswith('__'):
                continue
            if not hasattr(value, '__call__'):
                continue
            if not getattr(value, '__doc__'):
                raise TypeError(f"方法{key}必须添加docstring描述")
        return super().__new__(cls, name, bases, namespace, **kwargs)


class DaGongRen(metaclass=Model):
    def __init__(self, name):
        self.name = name

    def save(self):
        """ 这是方法描述 """
        print(f"save()方法,可以是保存到数据库的逻辑")

    def work(self):
        print(f"{self.name}在努力工作")


if __name__ == '__main__':
    pass

执行结果:

d8c44cb8833ff0a6b1d7efeb3e59681a.jpeg

代码中,我们定义了一个元类Model,在基于Model进行类的定义(也就是创建类对象)时,会调用__new__()方法。所以,我们将类定义的校验逻辑,添加到了该方法中。简单介绍一下校验逻辑:

1、以__打头的属性、方法,统一不做校验。

2、没有__call__属性的,粗略来说,就是不可调用的对象也不做检查。

3、经过1、2的逻辑,我们只会对方法进行检查,而如果一个方法对象没有__doc__属性的话,说明没有写文档字符串,则抛出TypeError的异常。

需要补充说明的是,当我们在元类的__new__方法中添加校验逻辑时,可以阻断类对象的创建,也就是类定义本身就会执行终止。如果我们只是需要添加警告提醒、不阻断类对象的创建,也可以将这些逻辑移到__init__()方法中。

动态注入

既然,我们可以通过元类进行类定义的检查,那么我们同样可以在类对象的真实创建过程进行一些额外的加工,比如统一添加类属性,统一添加方法等。这样来说,我们在代码复用的实现上,除了继承外,又多了一条途径。

比如,我们想通过元类给每个类都添加一个方法:inject_method

class Model(type):
    def __new__(cls, name, bases, namespace, **kwargs):
        namespace['inject_method'] = lambda self: f'这是一个动态添加到{self.__class__}中的方法'
        return super().__new__(cls, name, bases, namespace, **kwargs)


class DaGongRen(metaclass=Model):
    def __init__(self, name):
        self.name = name


class XueShengDang(metaclass=Model):
    def __init__(self, name):
        self.name = name


if __name__ == '__main__':
    zs = DaGongRen('张三')
    ls = XueShengDang('李四')
    print(zs.inject_method())
    print(ls.inject_method())

执行结果:

4d81bb8ec4274b6d410efc2832839ec0.jpeg

在代码中,我们定义了一个元类Model,并通过该元类定义了两个类,没有通过继承关系,我们通过元类的__new__()方法,给这两个类动态添加了一个inject_method()方法。

插件注册

有一种场景是,在一个基于插件化设计的系统中,我们需要能够动态地加载和使用插件,从而提供动态的系统扩展功能。这种场景需求,我们也可以通过元类来实现。

我们可以通过在系统中维护一个全局的插件类注册表,每次通过元类进行插件类的扩展时,都可以在全局的注册表中进行注册。

以代码来模拟这种场景:

plugin_registry = {}


class Plugin(type):
    def __new__(cls, name, bases, namespace, **kwargs):
        if 'execute' not in namespace or not hasattr(namespace['execute'], '__call__'):
            raise TypeError(f'{name}类未定义execute()方法')
        new_class = super().__new__(cls, name, bases, namespace, **kwargs)
        plugin_registry[name] = new_class
        return new_class


class PluginA(metaclass=Plugin):
    def execute(self):
        print(f"插件{self.__class__()}在运行")


class PluginB(metaclass=Plugin):
    def execute(self):
        print(f"插件{self.__class__()}在运行")


if __name__ == '__main__':
    for key, value in plugin_registry.items():
        value().execute()


执行结果:

a190a391858a5391aab58933438026b5.jpeg

总结

本文简单介绍了Python中元类在类定义检查、动态属性/方法注入、插件注册等场景中的使用。当然,这些功能在通常的业务场景中是用不到的。但是,如果有同学要阅读框架源码,或者打算自己开发框架,相信元类的知识一定会对你有所帮助。

感谢您的拨冗阅读,如果对您学习Python有所帮助,欢迎点赞、关注。

  • 10
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南宫理的日知录

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值