Как перезагрузить функцию модуля в Python? - PullRequest
32 голосов
/ 01 сентября 2011

В ответ на этот вопрос о перезагрузке модуля , как перезагрузить определенную функцию из измененного модуля?

псевдо-код:

from foo import bar

if foo.py has changed:
    reload bar

Ответы [ 7 ]

18 голосов
/ 18 октября 2017

На сегодняшний день правильный способ сделать это:

import sys, importlib
importlib.reload(sys.modules['foo'])
from foo import bar

Проверено на python 2.7, 3.5, 3.6.

10 голосов
/ 01 сентября 2011

Горячая перезагрузка - это не то, что вы можете сделать в Python надежно, не взорвав голову. Вы буквально не можете поддерживать перезагрузку без написания кода особым образом, а попытка написать и поддерживать код, который поддерживает перезагрузку с любым здравым смыслом, требует чрезвычайной дисциплины и слишком запутана, чтобы стоить усилий. Тестирование такого кода тоже не простая задача.

Решение состоит в том, чтобы полностью перезапустить процесс Python после изменения кода. Это можно сделать незаметно, но как это зависит от вашей конкретной проблемной области.

8 голосов
/ 01 сентября 2011

То, что вы хотите, возможно, но требует перезагрузки двух вещей: сначала reload(foo), но затем вам также нужно reload(baz) (при условии, что baz - это имя модуля, содержащего оператор from foo import bar).

Что касается того, почему ... Когда foo загружается впервые, создается объект foo, содержащий объект bar. Когда вы импортируете bar в модуль baz, он сохраняет ссылку на bar. Когда вызывается reload(foo), объект foo очищается, и модуль повторно выполняется. Это означает, что все ссылки foo все еще действительны, но новый объект bar создан ... поэтому все ссылки, которые были импортированы где-то, все еще являются ссылками на old bar объект. Перезагружая baz, вы заставляете его импортировать новый bar.


Кроме того, вы можете просто сделать import foo в своем модуле и всегда вызывать foo.bar(). Таким образом, всякий раз, когда вы reload(foo), вы получаете новейшую ссылку bar.

3 голосов
/ 03 марта 2015

Хотя перезагрузка функций не является функцией функции reload, она все еще возможна. Я бы не рекомендовал делать это в производстве, но вот как это работает: Функция, которую вы хотите заменить, является объектом где-то в памяти, и ваш код может содержать множество ссылок на него (а не на имя функции). Но то, что фактически делает эта функция при вызове, сохраняется не в этом объекте, а в другом объекте, на который, в свою очередь, ссылается объект функции в его атрибуте __code__. Поэтому, если у вас есть ссылка на функцию, вы можете обновить ее код:

Модуль mymod:

from __future__ import print_function
def foo():
    print("foo")

Другой сеанс модуля / Python:

>>> 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  # 
>>> 
2 голосов
/ 01 сентября 2011

Вы не можете перезагрузить метод из модуля, но вы можете загрузить модуль снова с новым именем, скажем foo2 и сказать bar = foo2.bar, чтобы перезаписать текущую ссылку.1005 * имеет какие-либо зависимости от других вещей в foo или любых других побочных эффектов, вы попадете в беду.Так что пока он работает, он работает только для самых простых случаев.

1 голос
/ 11 октября 2015

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

1 голос
/ 01 сентября 2011

Вам придется использовать reload для перезагрузки модуля, так как вы не можете перезагрузить только функцию:

>>> import sys
>>> reload(sys)
<module 'sys' (built-in)>
>>>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...