python中的反射及在动态导入中的使用详解

这篇博客记录下python中关于反射的知识点,同时结合实例看看其在动态导入中的应用。

什么是反射

反射(Reflect)是指面向对象编程中通过字符串动态获取对象的类型、属性和方法等信息。

反射使用场景

想象下面的场景,我们编写了几十个插件供业务程序来使用,而具体业务中使用哪些插件是由配置文件来控制的,或者由用户输入的。不管是配置文件还是用户输入,都是采用字符串形式来表示插件名,我们需要检查字符串代表的插件名是否存在,如果存在需要获取对应的插件对象并调用。

文字表述不太直观,看如下的一个项目

在这里插入图片描述

plugins模块中有两个插件,分别是plugin1plugin2

# plugin1
class Calc1:
    def process(self):
        print('class name is {}'.format(self.__class__.__name__))
# plugin2
class Calc2:
    def process(self):
        print('class name is {}'.format(self.__class__.__name__))

并在__init__.py中进行了导入

from plugins import plugin1, plugin2

__init__.py的使用感兴趣可以查看另一篇博客《python目录中的__init__.py文件详解》

具体的业务代码在app中,如下

import plugins

if __name__ == '__main__':
    name = input('请输入想要执行的plugin:')
    if name == 'plugin1':
        plugins.plugin1.Calc1().process()
    elif name == 'plugin2':
        plugins.plugin2.Calc2().process()

根据用户的输入来决定使用哪一个插件。因为用户的输入为字符串,不能通过类似name.Calc()的方法来调用,所以要加入if...else...的结构来进行判断。

看起来没什么问题。

但是往往一个项目中的插件数量高达几十个,如果都要用if...else...结构来判断的话代码就太臃肿了,有没有更简单快捷的方法呢?

就需要用到反射了。

反射的使用

python内置了4个函数用来实现反射的4种操作,分别是

hasattr(obj,str)  # 检查str对应的方法或者属性是否在obj对象内
getattr(obj,str)  # 获取obj中和str相同的方法或者属性
setattr(obj,str,val)  # 为obj对象设置一个名字为str,内容为val的方法或者属性
delattr(obj,str)  # 删除obj对象中和str相同的方法或者属性

下面简单测试下

import plugins

if __name__ == '__main__':
    name = input('请输入想要执行的plugin:')
    print(hasattr(plugins, name))

结果如下

请输入想要执行的plugin:plugin3
False
请输入想要执行的plugin:plugin1
True

可见hasattr()方法能顺利完成检测的目的。

修改下上面用if...else...实现的代码如下

import plugins

if __name__ == '__main__':
    name = input('请输入想要执行的plugin:')
    if hasattr(plugins, name):
        plugin = getattr(plugins, name)
        print(plugin)
    else:
        print('所输入的插件不存在')

结果如下

请输入想要执行的plugin:plugin1
<module 'plugins.plugin1' from 'C:\\Users\\Admin\\PycharmProjects\\Reflect\\plugins\\plugin1.py'>

可以看到,即使有几十个插件供检测这里的代码也不用变。

动态导入

下面对反射进行一点引申,来完成实际项目中很常见的一个功能:动态导入。

如果对Django比较熟悉的朋友可以类比配置文件中的中间件配置MIDDLEWARE部分,例如

MIDDLEWARE = [
    'debug_toolbar.middleware.DebugToolbarMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'middleware.middleware.loginMiddleware',
]

实际在业务中使用哪些中间件由这里的配置动态决定,也就是所谓的动态导入。因为这里使用字符串来表示具体的中间件类,所以需要使用反射来进行实际的获取和使用。

还是用上面的例子来实现一个我们自己的动态导入。

首先在配置文件settings.py中定义如下列表,分别将两个插件类的地址放在这里,注意是用点号来连接路径

PLUGINS = [
    'plugins.plugin1.Calc1',
    'plugins.plugin2.Calc2',
]

然后修改业务代码如下

from importlib import import_module
import settings

if __name__ == '__main__':
    for plugin in settings.PLUGINS:
        module_name, class_name = plugin.rsplit('.', maxsplit=1)
        module = import_module(module_name)
        if hasattr(module, class_name):
            obj = getattr(module, class_name)()
            obj.process()

针对settings.PLUGINS这个列表,循环处理其中的每一个元素。首先利用rsplit()方法分离出模块和类名,方便后续使用。然后导入模块,值得注意的是因为字符串不能直接被导入,还需要借助import_module()方法。最后就是我们前面提到的反射的使用,在模块中获取具体的类然后进行操作。值得注意的是,因为采用了动态导入,不需要再导入plugins模块了,也就避免了__init__.py文件书写错误导致的模块导入问题。

采用动态导入使得程序的可扩展性变得很高,插件的加减完全由配置文件来控制,不需要对业务代码进行任何修改,符合开闭原则。

我是T型人小付,一位坚持终身学习的互联网从业者。喜欢我的博客欢迎在csdn上关注我,如果有问题欢迎在底下的评论区交流,谢谢。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值