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