В ответ на этот вопрос о перезагрузке модуля, как я могу перезагрузить определенную функцию из измененного модуля?
псевдокода:
from foo import bar
if foo.py has changed:
reload bar
В ответ на этот вопрос о перезагрузке модуля, как я могу перезагрузить определенную функцию из измененного модуля?
псевдокода:
from foo import bar
if foo.py has changed:
reload bar
То, что вы хотите, возможно, но требует перезагрузки двух вещей... сначала reload(foo)
, но тогда вы также должны reload(baz)
(предполагая, что baz
- это имя модуля, содержащего оператор from foo import bar
).
Что касается того, почему... Когда сначала загружается foo
, создается объект foo
, содержащий объект bar
. Когда вы импортируете bar
в модуль baz
, он сохраняет ссылку на bar
. Когда вызывается reload(foo)
, объект foo
пуст, и модуль повторно выполняется. Это означает, что все ссылки foo
все еще действительны, но был создан новый объект bar
... так что все ссылки, которые были импортированы где-то, все еще ссылаются на старый объект bar
. Перезагрузив baz
, вы вызываете его в reimport новый bar
.
В качестве альтернативы вы можете просто сделать import foo
в своем модуле и всегда вызывать foo.bar()
. Таким образом, когда вы reload(foo)
, вы получите самую новую ссылку bar
.
Горячая перезагрузка - это не то, что вы можете сделать в Python надежно, не взорвав голову. Вы в буквальном смысле не можете поддерживать перезагрузку, не создавая специальных способов кода, а также пытаться писать и поддерживать код, поддерживающий перезагрузку с любой здравомыслием, требует крайней дисциплины и слишком запутанной, чтобы стоить усилий. Тестирование такого кода также нелегко.
Решение состоит в том, чтобы полностью перезапустить процесс Python, когда код изменился. Это можно сделать легко, но как это зависит от конкретной проблемной области.
В то время как перезагрузка функций не является функцией функции reload
, она по-прежнему возможна. Я бы не рекомендовал делать это на производстве, но вот как это работает:
Функция, которую вы хотите заменить, - это объект где-то в памяти, и ваш код может содержать много ссылок на него (а не на имя функции). Но то, что эта функция фактически выполняет при вызове, не сохраняется в этом объекте, а в другом объекте, который, в свою очередь, ссылается на объект функции, в его атрибуте __code__
. Поэтому, пока у вас есть ссылка на эту функцию, вы можете обновить ее код:
Модуль mymod:
from __future__ import print_function
def foo():
print("foo")
Другой сеанс модуля/питона:
>>> import mymod
>>> mymod.foo()
foo
>>> old_foo_ref = mymod.foo
>>> # edit mymod.py to make function "foo" print "bar"
>>> reload(mymod)
<module 'mymod' from 'mymod.py'>
>>> old_foo_ref() # old reference is running old code
foo
>>> mymod.foo() # reloaded module has new code
bar
>>> old_foo_ref.__code__ = mymod.foo.__code__
>>> old_foo_ref() # now the old function object is also running the new code
bar
>>>
Теперь, если у вас нет ссылки на старую функцию (т.е. lambda
, переданную в другую функцию), вы можете попытаться ее удержать с помощью модуля gc
, выполнив поиск в списке все объекты:
>>> def _apply(f, x):
... return lambda: f(x)
...
>>> tmp = _apply(lambda x: x+1, 1) # now there is a lambda we don't have a reference to
>>> tmp()
2
>>> import gc
>>> lambdas = [obj for obj in gc.get_objects() if getattr(obj,'__name__','') == '<lambda>'] # get all lambdas
[<function <lambda> at 0x7f315bf02f50>, <function <lambda> at 0x7f315bf12050>]
# i guess the first one is the one i passed in, and the second one is the one returned by _apply
# lets make sure:
>>> lambdas[0].__code__.co_argcount
1 # so this is the "lambda x: ..."
>>> lambdas[1].__code__.co_argcount
0 # and this is the "lambda: ..."
>>> lambdas[0].__code__ = (lambda x: x+2).__code__ # change lambda to return arg + 2
>>> tmp()
3 #
>>>
На сегодняшний день правильный способ сделать это:
import sys, importlib
importlib.reload(sys.modules['foo'])
from foo import bar
Протестировано на python 2.7, 3.5, 3.6.
Вы не можете перезагрузить метод из модуля, но вы можете снова загрузить модуль с новым именем, например foo2
и сказать bar = foo2.bar
, чтобы перезаписать текущую ссылку.
Обратите внимание, что если bar
имеет какие-либо зависимости от других вещей в foo
или любых других побочных эффектах, вы столкнетесь с проблемами. Поэтому, пока он работает, он работает только для самых простых случаев.
Вам нужно будет использовать reload
для перезагрузки модуля, так как вы не можете перезагрузить только функцию:
>>> import sys
>>> reload(sys)
<module 'sys' (built-in)>
>>>
Если вы являетесь автором foo.py
, вы можете написать:
with open('bar.py') as f: bar_code=compile(f.read(),'bar.py','exec')
def bar(a,b):
exec(bar_code)
def reload_bar():
global bar_code
with open('bar.py') as f: bar_code=compile(f.read(),'bar.py','exec')
Затем в псевдокоде перезагрузите, если bar.py
изменилось. Этот подход особенно хорош, когда бар живет в том же модуле, что и код, который хочет его перезагрузить, а не в случае OP, где он живет в другом модуле.