Python运行程序热插拔

发布于 2024-11-25 05:45:06 字数 3922 浏览 0 评论 0原文

以下代码允许您在运行时修改 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() 方法,该方法会重新加载源文件。

在我的实现中,更改检测基于每隔频率秒轮询检查文件的 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 代码。然而,我喜欢能够稍微了解一下运行时并意识到什么是可能的,什么是不可能的。请注意,加载机制可以添加锁(如果正在使用资源)和异常处理(如果模块未成功加载)。

评论?

The following code allows you to modify the contents of runtime.py at run time. In other words, you don't have to interrupt 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()

The module imported at runtime is:

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

This primitive mechanism allows you to "how-swap" Python code (a la Erlang). Is there a better alternative?

Please notice that this is a merely academic question, as I don't have the necessity to do anything like this. However, I am interested in learning more about the Python runtime.

Edit:

I created the following solution: an Engine object provides an interface to the functions contained in a module (in this case the module is called engine.py). The Engine object also spawns a thread which monitors for changes in the source file and, if changes are detected, it calls the notify() method on the engine, which reloads the source file.

In my implementation, the change detection is based on polling every frequency seconds checking the SHA1 checksum of the file, but other implementations are possible.

In this example every change detected is logged to a file called hotswap.log, where the checksum is registered.

Other mechanisms for detecting changes could be a server or the use of inotify in the Monitor thread.

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

The engine.py file:

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

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

Log sample:

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)

Again - this is an academic discussion because I have no need at this moment of hot-swapping Python code. However, I like being able to understand a little bit the runtime and realize what is possible and what is not. Notice that the loading mechanism could add a lock, in case it is using resources, and exception handling, in case the module is not loaded successfully.

Comments?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

无所谓啦 2024-12-02 05:45:06

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

reload(runtime)

任何时候我在调试 python 模块时,我都会在交互式 python 命令提示符中使用这种方法(除非我手动调用 reload(),否则我不会轮询任何内容)。

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

You could poll the runtime.py file, waiting for it to change. Once it changes, just call

reload(runtime)

Any time I'm debugging a python module, I use this approach in the interactive python command prompt (except I manually call reload(), I don't poll anything).

EDIT:
To detect changes in a file, check out this SO question. Polling may be the most reliable option, but I would only reload the file if the modified time is updated, rather than reloading it on every poll. You should also consider catching exceptions when you reload, especially syntax errors. And you may or may not encounter problems with thread safety.

未蓝澄海的烟 2024-12-02 05:45:06
globe = __import__('copy').copy(globals())
while True:
    with open('runtime.py', 'r') as mod:
        exec mod in globe
    __import__('time').sleep(1)

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

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

Will repeatedly read and run runtime.py with a nearly unpolluted globals() and no locals(), and won't pollute the global scope, but all of runtime's namespace will be available in globe

一枫情书 2024-12-02 05:45:06

如果您想要在使用函数导入等时找到的热插拔代码,则需要覆盖模块的全局变量,例如您使用:

import mylib

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

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

If you want hot-swap code that found as when you use import out of functions etc, you need overwrite a global var of module, if for example you use:

import mylib

You need when load module in code asign to mylib the new module. Other cuestion is try this in program that use threads for know if is secure with threads and, when use multiprocessing this only found in one process, for change code in all process all need load new code, is necesary try if is secure in multiproces.

And, is interessting check first if have new code or not for not load same code. And think in Python only you can load a new module and replace variable name of module, but if you really need a good hot change code see Erlang language and OTP, it's very good.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文