Comment recharger la fonction d'un module en Python?
7 réponses
ce que vous voulez est possible, mais nécessite de recharger deux choses... d'abord reload(foo)
, mais ensuite vous devez aussi reload(baz)
(en supposant que baz
est le nom du module contenant la déclaration from foo import bar
).
quant à la raison... Lorsque foo
est chargé pour la première fois, un objet foo
est créé, contenant un objet bar
. Lorsque vous importez bar
dans le module baz
, il stocke une référence à bar
. Quand reload(foo)
est appelé, l'objet foo
est effacé, et le module ré-exécuté. Cela signifie que toutes les références foo
sont toujours valables, mais un nouvel objet bar
a été créé... donc toutes les références qui ont été importées quelque part sont toujours des références à l'objet Vieux bar
. En rechargeant baz
, vous lui faites réimporter le nouveau bar
.
alternativement, vous pouvez juste faire import foo
en votre module, et toujours appeler foo.bar()
. De cette façon , à chaque fois que vous reload(foo)
, vous obtiendrez la plus récente référence bar
.
recharger à chaud n'est pas quelque chose que vous pouvez faire en python de manière fiable sans faire exploser votre tête. Vous ne pouvez littéralement pas supporter le rechargement sans avoir écrit le code de manière spéciale, et essayer d'écrire et de maintenir le code qui supporte le rechargement avec n'importe quelle raison exige une discipline extrême et est trop confus pour être la peine de l'effort. Tester un tel code n'est pas une tâche facile non plus.
la solution est de redémarrer complètement le processus Python lorsque le code a changé. Il est possible de faites cela de manière transparente, mais comment dépend de votre domaine de problème spécifique.
à partir d'aujourd'hui, la bonne façon de faire ceci est:
import sys, importlib
importlib.reload(sys.modules['foo'])
from foo import bar
testé sur python 2.7, 3.5, 3.6.
alors que le rechargement des fonctions n'est pas une caractéristique de la fonction reload
, il est encore possible. Je ne recommande pas de le faire en production, mais voici comment cela fonctionne:
La fonction que vous voulez remplacer est un objet quelque part dans la mémoire, et votre code pourrait contenir de nombreuses références (et non le nom de la fonction). Mais ce que cette fonction fait réellement lorsqu'elle est appelée n'est pas sauvegardée dans cet objet, mais dans un autre objet qui, à son tour, est référencé par l'objet de la fonction, dans son l'attribut __code__
. Donc, tant que vous avez une référence à la fonction, vous pouvez mettre à jour son code:
Module mymod:
from __future__ import print_function
def foo():
print("foo")
autre module / session 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
>>>
maintenant, dans le cas où vous n'avez pas de référence à l'ancienne fonction (i.e. un lambda
passé dans une autre fonction), vous pouvez essayer de mettre la main dessus avec le module gc
, en cherchant la liste de tous les objets: "15198090920"
>>> 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 #
>>>
Vous ne pouvez pas recharger une méthode à partir d'un module, mais vous pouvez charger le module avec un nouveau nom, dire foo2
et dire bar = foo2.bar
pour remplacer la référence actuelle.
notez que si bar
a des dépendances sur d'autres choses dans foo
ou tout autre effet secondaire, vous allez avoir des problèmes. Donc, bien que cela fonctionne, cela ne fonctionne que pour les cas les plus simples.
vous devrez utiliser reload
pour recharger le module, car vous ne pouvez pas recharger seulement la fonction:
>>> import sys
>>> reload(sys)
<module 'sys' (built-in)>
>>>
Si vous êtes l'auteur de foo.py
, vous pouvez écrire:
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')
puis, dans votre pseudo, rechargez si bar.py
a changé. Cette approche est particulièrement agréable lorsque bar vit dans le même module que le code qui veut le recharger à chaud plutôt que dans le cas de L'OP où il vit dans un module différent.