引言
在上一篇文章中,我们简单聊了一下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
执行结果:
代码中,我们定义了一个元类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())
执行结果:
在代码中,我们定义了一个元类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()
执行结果:
总结
本文简单介绍了Python中元类在类定义检查、动态属性/方法注入、插件注册等场景中的使用。当然,这些功能在通常的业务场景中是用不到的。但是,如果有同学要阅读框架源码,或者打算自己开发框架,相信元类的知识一定会对你有所帮助。
感谢您的拨冗阅读,如果对您学习Python有所帮助,欢迎点赞、关注。