Имам дългогодишен сървър на Python и бих искал да мога да обновя услуга, без да рестартирам сървъра. Какъв е най-добрият начин да го направя?
if foo.py has changed:
unimport foo <-- How do I do this?
import foo
myfoo = foo.Foo()
Можете да презаредите модул, когато той вече е импортиран, като използвате вградената функция reload
:
from importlib import reload # Python 3.4+ only.
import foo
while True:
# Do some things.
if is_changed(foo):
foo = reload(foo)
В Python 3 функцията reload
беше преместена в модула imp
. В версия 3.4 модулът imp
беше отстранен в полза на importlib
, а reload
беше добавен към последния. Когато се насочвате към версия 3 или по-нова, или направете препратка към съответния модул при извикването на reload
, или го импортирайте.
Мисля, че това е, което искате. Уеб сървърите като сървъра за разработка на Django'използват това, за да можете да видите ефекта от промените в кода си, без да рестартирате самия процес на сървъра.
Цитирам от документацията:
Кодът на Python модулите се прекомпилира и кодът на ниво модул се изпълнява отново,
дефиниране на нов набор от обекти, които се свързват с имена в модула речника. Функцията init на модулите за разширение не се нарича втори път. Както при всички други обекти в Python старите обекти са само възстановени, след като броят на техните референции падне до нула. Имената в модула се актуализират, за да сочат към всички нови или променени обекти. Други препратки към старите обекти (като например външни за модула имена) не се пренасочват към новите обекти и трябва да се актуализират във всяко пространство от имена където се срещат, ако това е желателно.
Както сте отбелязали във въпроса си, ще трябва да възстановите обектите Foo
, ако класът Foo
се намира в модула foo
.
Изтриването на модул може да бъде особено трудно, ако той не е чист Python.
Ето малко информация от: Как наистина да изтрия импортиран модул?
Можете да използвате sys.getrefcount(), за да разберете действителния брой на референции.
>>> import sys, empty, os
>>> sys.getrefcount(sys)
9
>>> sys.getrefcount(os)
6
>>> sys.getrefcount(empty)
3
Числата, по-големи от 3, показват, че ще бъде трудно да се отървете от модул. Домашният "празен" (не съдържащ нищо) трябва да бъде да бъде изхвърлен на боклука след
>>> del sys.modules["empty"]
>>> del empty
тъй като третата препратка е артефакт на функцията getrefcount().
reload(module)
, но само ако е напълно самостоятелен. Ако нещо друго има препратка към модула (или към някой обект, принадлежащ на модула), тогава ще се получат фини и любопитни грешки, причинени от това, че старият код се е задържал по-дълго, отколкото сте очаквали, и неща като isinstance
не работят в различни версии на същия код.
Ако имате еднопосочни зависимости, трябва също така да презаредите всички модули, които зависят от презаредения модул, за да се отървете от всички препратки към стария код. И след това да презаредите модулите, които зависят от презаредените модули, рекурсивно.
Ако имате кръгови зависимости, което е много често срещано, например когато се занимавате с презареждане на пакет, трябва да презаредите всички модули в групата наведнъж. Не можете да направите това с reload()
, защото той ще импортира отново всеки модул, преди да са опреснени неговите зависимости, което ще позволи стари препратки да се промъкнат в новите модули.
Единственият начин да го направите в този случай е да хакнете sys.modules
, което е малко неподдържано. Ще трябва да преминете и да изтриете всеки запис в sys.modules
, който искате да бъде презареден при следващото импортиране, както и да изтриете записите, чиито стойности са None
, за да се справите с проблема с имплементацията, свързан с кеширането на неуспешни относителни импорти. Това не е много приятно, но докато имате напълно самостоятелен набор от зависимости, който не оставя препратки извън кодовата си база, това е работещо.
Вероятно е най-добре да рестартирате сървъра. :-)