【问题标题】:Hot-swapping of Python running programPython运行程序的热插拔
【发布时间】:2011-10-08 17:44:09
【问题描述】:

以下代码允许您在运行时修改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()

运行时导入的模块是:

# runtime.py
def function():
    print("I am version one of runtime.py")

这种原始机制允许您“如何交换”Python 代码(类似于 Erlang)。有更好的选择吗?

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

编辑

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

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

在此示例中,检测到的每个更改都记录到名为 hotswap.log 的文件中,其中注册了校验和。

检测更改的其他机制可能是服务器或在Monitor 线程中使用inotify

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()

engine.py 文件:

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

def f2():
    print("call to f2")

日志样本:

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)

再次 - 这是一个学术讨论,因为此时我不需要热交换 Python 代码。但是,我喜欢能够理解一点运行时并意识到什么是可能的,什么是不可能的。请注意,加载机制可以添加一个锁,以防它正在使用资源,以及异常处理,以防模块未成功加载。

评论?

【问题讨论】:

标签: python hotswap


【解决方案1】:
globe = __import__('copy').copy(globals())
while True:
    with open('runtime.py', 'r') as mod:
        exec mod in globe
    __import__('time').sleep(1)

将反复读取和运行runtime.py,几乎未受污染的globals() 和没有locals(),不会污染全局范围,但所有运行时的命名空间都将在globe 中可用

【讨论】:

  • 这很有趣,但我不会放松命名空间吗?
  • 是的,你会的。我认为runner.py 只是从runtime.py 重复重新加载和重新执行代码的助手,所以你不在乎。您需要复制一份 globals(),然后进行编辑以显示。
【解决方案2】:

您可以轮询 runtime.py 文件,等待它更改。一旦它改变,只需调用

reload(runtime)

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

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

【讨论】:

  • 其实函数reload和imp.load_source是等价的。我想我对代理重新加载机制的“轮询”或“侦听器”机制更感兴趣。谢谢!
  • 请注意,对于 Python 3.4+,您需要使用“from importlib import reload”,而对于早期版本,您需要使用“from imp import reload”
【解决方案3】:

如果您想要在使用 import out of functions 等时发现的热交换代码,则需要覆盖模块的全局 var,例如,如果您使用:

import mylib

在代码中加载模块时需要将新模块分配给 mylib。其他提示是在使用线程的程序中尝试此操作以了解线程是否安全,并且当使用多处理时,这仅在一个进程中发现,对于所有进程中的更改代码都需要加载新代码,是否有必要尝试在多进程中是否安全。

而且,有趣的是,首先检查是否有新代码是否不加载相同的代码。并且认为只有在 Python 中你才能加载新模块并替换模块的变量名,但如果你真的需要一个好的热更改代码,请参阅 Erlang 语言和 OTP,它非常好。

【讨论】:

  • 使用 Erlang/OTP 的建议我认为在需要热插拔代码好、容错软件、并发或分布式执行时是最好的解决方案。 Whatsapp 在 Erlang 中运行服务器,Facebook 与 Facebook 聊天相同,Tuenti 也是,使命召唤服务器等。
猜你喜欢
  • 2017-03-14
  • 2014-04-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-26
  • 2014-05-05
  • 2014-02-19
相关资源
最近更新 更多