Подтвердить что ты не робот

Горячая замена программы запуска Python

Следующий код позволяет вам изменять содержимое 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 (a la Erlang). Есть ли лучшая альтернатива?

Обратите внимание, что это просто академический вопрос, так как у меня нет необходимости делать что-либо подобное. Тем не менее, мне интересно узнать больше о времени выполнения Python.

Edit

Я создал следующее решение: объект 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()

Файл 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 с горячей заменой. Однако мне нравится понимать немного времени исполнения и понимать, что возможно, а что нет. Обратите внимание, что механизм загрузки может добавить блокировку, если он использует ресурсы и обработку исключений, если модуль не загружен успешно.

Комментарии?

4b9b3361

Ответ 1

Вы можете опросить файл runtime.py, ожидая его изменения. Как только он изменится, просто позвоните

reload(runtime)

Каждый раз, когда я отлаживаю модуль python, я использую этот подход в интерактивной командной строке python (за исключением того, что я вручную вызываю reload(), я ничего не опробую).

EDIT: Чтобы обнаружить изменения в файле, ознакомьтесь с этим вопросом SO. Опрос может быть самым надежным вариантом, но я бы только перезагрузил файл, если измененное время обновлено, а не перезагружать его при каждом опросе. Вы также должны учитывать исключения при перезагрузке, особенно синтаксические ошибки. И вы можете столкнуться или не столкнуться с проблемами безопасности потоков.

Ответ 2

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() и no locals() и не будет загрязнять глобальную область видимости, но все пространство пространства исполнения будет доступно в globe

Ответ 3

Если вам нужен код "горячей" замены, который был найден, когда вы используете импорт из функций и т.д., вам нужно перезаписать глобальный var модуля, если вы, например, используете:

import mylib

Вам нужно, когда загрузочный модуль в коде присвоит mylib новый модуль. Другой cuestion попробует это в программе, которая использует потоки для того, чтобы знать, защищена ли она потоками, и, когда вы используете многопроцессорность, это только в одном процессе, для кода изменения во всех процессах все нужно загрузить новый код, необходимо попробовать, если он безопасен в многопроцессах.

И, прежде всего, проверьте, есть ли новый код или нет для того, чтобы не загружать тот же код. И подумайте только на Python, вы можете загрузить новый модуль и заменить имя переменной модуля, но если вам действительно нужен хороший код смены, см. Erlang и OTP, это очень хорошо.