热插拔Python运行程序

Esc*_*alo 18 python hotswap

以下代码允许您修改runtime.py运行时的内容.换句话说,您不必中断runner.py.

#runner.py
import time
import imp

def main():
    while True:
        mod = imp.load_source("runtime", "./runtime.py")
        mod.function()
        time.sleep(1)

if __name__ == "__main__":
    main()
Run Code Online (Sandbox Code Playgroud)

在运行时导入的模块是:

# runtime.py
def function():
    print("I am version one of runtime.py")
Run Code Online (Sandbox Code Playgroud)

这种原始机制允许您"如何交换"Python代码(la Erlang).还有更好的选择吗?

请注意,这只是一个学术问题,因为我没有必要做这样的事情.但是,我有兴趣了解有关Python运行时的更多信息.

编辑:

我创建了以下解决方案:一个Engine对象提供了一个模块中包含的函数的接口(在这种情况下,模块被调用engine.py).该Engine对象还生成一个线程,用于监视源文件中的更改,如果检测到更改,则会调用notify()引擎上的方法,该方法会重新加载源文件.

在我的实现中,更改检测基于每秒frequency检查文件的SHA1校验和的轮询,但是其他实现也是可能的.

在此示例中,检测到的每个更改都会记录到已调用hotswap.log校验和的文件中.

用于检测改变的其他机制可以是服务器或使用inotify中的Monitor线程.

import imp
import time
import hashlib
import threading
import logging

logger = logging.getLogger("")

class MonitorThread(threading.Thread):
    def __init__(self, engine, frequency=1):
        super(MonitorThread, self).__init__()
        self.engine = engine
        self.frequency = frequency
        # daemonize the thread so that it ends with the master program
        self.daemon = True 

    def run(self):
        while True:
            with open(self.engine.source, "rb") as fp:
                fingerprint = hashlib.sha1(fp.read()).hexdigest()
            if not fingerprint == self.engine.fingerprint:
                self.engine.notify(fingerprint)
            time.sleep(self.frequency)

class Engine(object):
    def __init__(self, source):
        # store the path to the engine source
        self.source = source        
        # load the module for the first time and create a fingerprint
        # for the file
        self.mod = imp.load_source("source", self.source)
        with open(self.source, "rb") as fp:
            self.fingerprint = hashlib.sha1(fp.read()).hexdigest()
        # turn on monitoring thread
        monitor = MonitorThread(self)
        monitor.start()

    def notify(self, fingerprint):
        logger.info("received notification of fingerprint change ({0})".\
                        format(fingerprint))
        self.fingerprint = fingerprint
        self.mod = imp.load_source("source", self.source)

    def __getattr__(self, attr):
        return getattr(self.mod, attr)

def main():
    logging.basicConfig(level=logging.INFO, 
                        filename="hotswap.log")
    engine = Engine("engine.py")
    # this silly loop is a sample of how the program can be running in
    # one thread and the monitoring is performed in another.
    while True:
        engine.f1()
        engine.f2()
        time.sleep(1)

if __name__ == "__main__":
    main()
Run Code Online (Sandbox Code Playgroud)

engine.py文件中:

# this is "engine.py"
def f1():
    print("call to f1")

def f2():
    print("call to f2")
Run Code Online (Sandbox Code Playgroud)

记录样本:

INFO:root:received notification of fingerprint change (be1c56097992e2a414e94c98cd6a88d162c96956)
INFO:root:received notification of fingerprint change (dcb434869aa94897529d365803bf2b48be665897)
INFO:root:received notification of fingerprint change (36a0a4b20ee9ca6901842a30aab5eb52796649bd)
INFO:root:received notification of fingerprint change (2e96b05bbb8dbe8716c4dd37b74e9f58c6a925f2)
INFO:root:received notification of fingerprint change (baac96c2d37f169536c8c20fe5935c197425ed40)
INFO:root:received notification of fingerprint change (be1c56097992e2a414e94c98cd6a88d162c96956)
INFO:root:received notification of fingerprint change (dcb434869aa94897529d365803bf2b48be665897)
Run Code Online (Sandbox Code Playgroud)

再次 - 这是一个学术讨论,因为我现在不需要热插拔Python代码.但是,我喜欢能够理解一点运行时并意识到什么是可能的,什么不是.请注意,如果模块未成功加载,则加载机制可以在使用资源时添加锁,并进行异常处理.

评论?

Phi*_*hil 7

您可以轮询runtime.py文件,等待它更改.一旦改变,只需打电话

reload(runtime)
Run Code Online (Sandbox Code Playgroud)

每次我调试python模块时,我都会在交互式python命令提示符中使用这种方法(除了我手动调用reload(),我不会轮询任何东西).

编辑:要检测文件中的更改,请查看此SO问题.轮询可能是最可靠的选项,但我只会在更新修改时间时重新加载文件,而不是在每次轮询时重新加载它.您还应该考虑在重新加载时捕获异常,尤其是语法错误.您可能会遇到也可能不会遇到线程安全问题.


agf*_*agf 1

globe = __import__('copy').copy(globals())
while True:
    with open('runtime.py', 'r') as mod:
        exec mod in globe
    __import__('time').sleep(1)
Run Code Online (Sandbox Code Playgroud)

runtime.py将使用几乎未污染的globals()和 no重复读取和运行locals(),并且不会污染全局范围,但所有运行时的命名空间都将在globe

  • 是的,你会的。我假设 `runner.py` 只是一个从 `runtime.py` 重复重新加载和重新执行代码的助手,所以你不在乎。您需要制作“globals()”的副本,并进行编辑以显示该副本。 (2认同)