当前位置: 首页 > news >正文

python 在进程中动态加载模块

背景

在web 运维平台, 我们有时候希望创建部署一个运维脚本, 此时,传统方式: 新建脚本 》部署脚本》重启服务
很多时刻我们不想重启服务, 那么有没有不重启服务依旧可以加载新建的脚本呢? 接下来就是解决方案:

原理

  1. 获取脚本code
  2. 通过compile 加载code 到进程中
  3. 通过python 的反射机制获取脚本中的方法并执行

结构

  • base_handler.py – 通过反射获取function
  • schedul.py – 加载code 并调度
  • tese.py – 测试脚本

base_handler.py

import inspect
import sys


class BaseHandler(object):
    """
    所有脚本的基类BaseHandler
    """

    # 继承类必须实现的方法
    callbacks = ['execute', 'callback']

    @classmethod
    def _run_func(cls, function, *arguments):
        """
        执行指定函数
        """
        args, varargs, keywords, defaults = inspect.getargspec(function)
        ret = function(*arguments[:len(args) - 1])
        return ret

    @classmethod
    def _run_task(cls, task, response, callback):
        """
        找对对应函数
        """
        if not hasattr(cls, callback):
            raise NotImplementedError("self.%s() not implemented!" % callback)

        function = getattr(cls, callback)
        return cls._run_func(function, task, response)

    @classmethod
    def run_task(cls, module, task, response, callback):
        """
        舆情脚本运行当前任务,获取异常日志,返回ProcessorResult对象
        """
        exception = None
        stdout = sys.stdout
        results = []
        sub_tasks = []
        try:
            results, sub_tasks = cls._run_task(task, response, callback)

        except Exception as e:
            exception = e
        finally:
            sys.stdout = stdout
            # logs = list(module.log_buffer)
            # module.log_buffer[:] = []
        return results, sub_tasks

schedul.py

# coding: utf-8

import imp
import inspect
import linecache
import sys
import six


class ProjectLoader(object):
    """
    加载并执行字符串类型的python 代码
    project is dict
        示例:
        project = { 'script_name': 'xxx', 'code': code}
    m = ProjectLoader(project)
    """

    def __init__(self, project, mod=None):
        self.project = project
        self.name = project.get('script_name')
        self.mod = mod

    def load_module(self):
        if self.mod is None:
            self.mod = mod = imp.new_module(self.name)
        else:
            mod = self.mod
        mod.__file__ = '<%s>' % self.name
        mod.__loader__ = self
        mod.__project__ = self.project
        mod.__package__ = ''
        # 获取code
        code = self.get_code()
        # 执行
        six.exec_(code, mod.__dict__)
        # 清除缓存
        linecache.clearcache()
        if sys.version_info[:2] == (3, 3):
            sys.modules[self.name] = mod
        return mod

    def get_code(self):
        return compile(self.get_source(), '<%s>' % self.name, 'exec')

    def get_source(self):
        script = self.project['code']
        if isinstance(script, six.text_type):
            return script.encode('utf8')
        return script


if __name__ == '__main__':
    from celery_app import base_handler
    
    # 这里测试, 我直接从文件中读, 你可以试着通过API 从数据库中获取脚本code
    with open('test.py', 'r', encoding="utf-8") as f:
        code = f.read()
    # 测试code info
    obj = {
        'script_name': 'test',
        'code': code
    }
    # 加载code 到进程
    instance = ProjectLoader(obj)
    module = instance.load_module()
    # 获取脚本类
    if '__handler_cls__' not in module.__dict__:
        BaseHandler = module.__dict__.get('BaseHandler',
                                          base_handler.BaseHandler)
        for each in list(six.itervalues(module.__dict__)):
            if inspect.isclass(each) and each is not BaseHandler \
                    and issubclass(each, BaseHandler):
                module.__dict__['__handler_cls__'] = each
    instance = module.__dict__.get('__handler_cls__')
    # 把meta 信息放到脚本实例中
    instance.project_name = obj['script_name']
    instance.project = obj
    assert instance is not None, "need BaseHandler in project module"

    # 开始调用脚本中的方法
    instance.run_task(module, 456, 123456, 'callback')
    print(module)

test.py

# coding: utf-8

from base_handler import BaseHandler


class TestC(BaseHandler):

    def __init__(self, config):
        self.config = config

    @classmethod
    def execute(self, v, v2):
        print(v, v2)

    @classmethod
    def callback(self, v, v2):
        print(v, v2)

相关文章:

  • C++复试笔记(一)
  • OpenEuler-24.03-LTS-SP1安装
  • 解决VS Code中Python模块导入路径问题的完整指南
  • 【C语言】数组篇
  • 大模型系列课程学习-基于Vllm/Ollama/Ktransformers完成Deepseek推理服务部署
  • Meta 计划在 Llama 4 中引入改进的语音功能,接近双向自然对话
  • DeepSeek 与 ChatGPT的主要区别
  • Browser-Use在UI自动化测试中的应用
  • vue源码(二)
  • Ae 效果详解:VR 发光
  • 【已解决】MobaXterm中X11-Forwarding无法使用
  • Javascript 函数
  • Web3 的去中心化治理:如何实现透明与公正
  • 【后端开发】go-zero微服务框架实践(goland框架对比,go-zero开发实践,文件上传问题优化等等)
  • Elasticsearch:“Your trial license is expired”
  • 学习LED驱动知识(二)
  • 使用kolla-ansible单点部署openstack云平台
  • MySQL 值为Null会导致的问题
  • 【docker简化部署有状态prometheus+grafana】
  • Linux基础之基础概念
  • 印称一名高级官员在巴基斯坦发动的袭击中死亡
  • 巴基斯坦对印度发起网络攻击,致其约70%电网瘫痪
  • 巴军事行动致印度70%电网瘫痪
  • 花2万多在海底捞办婚礼,连锁餐企要抢酒楼的婚宴生意?
  • 金地集团:今年前4个月实现销售额109.3亿元,同比下降52.44%
  • 独家丨刘家琨获普利兹克奖感言:守护原始的感悟力