Suite à cette question concernant le rechargement d'un module , comment puis-je recharger une fonction spécifique à partir d'un module modifié?
pseudo-code:
from foo import bar
if foo.py has changed:
reload bar
Ce que vous voulez est possible, mais nécessite le rechargement de deux choses ... d'abord reload(foo)
, mais vous devez également reload(baz)
(en supposant que baz
est le nom du module contenant l'instruction from foo import bar
).
Quant à pourquoi ... 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
. Lorsque reload(foo)
est appelé, l'objet foo
est supprimé et le module réexécuté. Cela signifie que toutes les références foo
sont toujours valables, mais qu'un nouvel objet bar
a été créé ... de sorte que toutes les références importées quelque part sont toujours des références à l'objet old bar
. En rechargeant baz
, vous faites réimporter la nouvelle bar
.
Alternativement, vous pouvez simplement faire import foo
dans votre module et toujours appeler foo.bar()
. Ainsi, chaque fois que vous reload(foo)
, vous obtiendrez la dernière référence bar
.
Le rechargement à chaud n’est pas quelque chose que vous pouvez faire en Python de manière fiable sans vous casser la tête. Vous ne pouvez littéralement pas prendre en charge le rechargement sans écrire du code de manière spéciale, et essayer d'écrire et de maintenir du code qui permet de recharger avec sagesse nécessite une discipline extrême et est trop déroutant pour en valoir la peine. Tester un tel code n'est pas une tâche facile non plus.
La solution consiste à redémarrer complètement le processus Python lorsque le code a été modifié. Il est possible de le faire de manière transparente, mais cela dépend de votre domaine de problème spécifique.
À ce jour, la bonne façon de procéder est la suivante:
import sys, importlib
importlib.reload(sys.modules['foo'])
from foo import bar
Testé sur python 2.7, 3.5, 3.6.
Bien que le rechargement de fonctions ne soit pas une fonctionnalité de la fonction reload
, il est toujours possible. Je ne recommanderais pas de le faire en production, mais voici comment cela fonctionne: La fonction que vous voulez remplacer est un objet situé quelque part en mémoire, et votre code peut contenir de nombreuses références à celui-ci (et non au nom de la fonction). Mais ce que cette fonction fait réellement lorsqu'il est appelé n'est pas enregistré dans cet objet, mais dans un autre objet qui, à son tour, est référencé par l'objet fonction, dans son 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, si vous n'avez pas de référence à l'ancienne fonction (c'est-à-dire une lambda
passée dans une autre fonction), vous pouvez essayer de la récupérer avec le module gc
en recherchant dans la liste de tous les objets:
>>> 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 le charger à nouveau avec un nouveau nom, par exemple, foo2
et bar = foo2.bar
pour remplacer la référence actuelle.
Notez que si bar
a des dépendances sur d’autres éléments de foo
ou sur d’autres effets secondaires, vous aurez des problèmes. Ainsi, bien que cela fonctionne, cela ne fonctionne que pour les cas les plus simples.
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')
Ensuite, dans votre pseudocode, rechargez si bar.py
a changé. Cette approche est particulièrement agréable lorsque bar réside dans le même module que le code qui veut le recharger à chaud plutôt que dans le cas du PO où il réside dans un module différent.
Vous devrez utiliser reload
pour recharger le module, car vous ne pouvez pas recharger uniquement la fonction:
>>> import sys
>>> reload(sys)
<module 'sys' (built-in)>
>>>