Горячая замена запущенной программы 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 (а-ля 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()
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. Однако мне нравится немного понимать время выполнения и понимать, что возможно, а что нет. Обратите внимание, что механизм загрузки может добавить блокировку, если он использует ресурсы, и обработку исключений, если модуль не был загружен успешно.
Комментарии?
3 ответа
Вы можете опросить файл runtime.py, ожидая его изменения. Как только это изменится, просто позвоните
reload(runtime)
Каждый раз, когда я отлаживаю модуль python, я использую этот подход в интерактивной командной строке python (за исключением того, что я вручную вызываю reload(), я ничего не опрашиваю).
РЕДАКТИРОВАТЬ: Чтобы обнаружить изменения в файле, проверьте этот вопрос SO. Опрос может быть наиболее надежным вариантом, но я бы перезагрузил файл, только если измененное время обновляется, а не перезагружал его при каждом опросе. Вы должны также рассмотреть возможность перехвата исключений при перезагрузке, особенно синтаксических ошибок. И вы можете или не можете столкнуться с проблемами безопасности потоков.
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
Если вам нужен код горячей замены, который был найден при импорте из функций и т. Д., Вам нужно перезаписать глобальную переменную модуля, если, например, вы используете:
import mylib
Вам нужно при загрузке модуля в коде присвоить mylib новый модуль. Другой вопрос - попробуйте это в программе, использующей потоки, чтобы узнать, защищен ли поток с потоками, и, если используется многопроцессорная обработка, это можно найти только в одном процессе, для изменения кода во всех процессах необходимо загрузить новый код, это необходимо, если это безопасно в многопроцессорных системах.
И, интересно сначала проверить, есть ли новый код или нет для того, чтобы не загружать тот же код. И подумайте, только в Python вы можете загрузить новый модуль и заменить имя переменной модуля, но если вам действительно нужен хороший горячий код изменения, посмотрите язык Erlang и OTP, это очень хорошо.