Lors du développement d'un projet volumineux (divisé en plusieurs fichiers et dossiers) en Python avec IPython, je suis tombé dans le pétrin des modules importés mis en cache.
Le problème est que les instructions import module
ne lisent le module qu'une seule fois, même si ce module a changé! Donc, chaque fois que je change quelque chose dans mon paquet, je dois quitter et redémarrer IPython. Douloureux.
Existe-t-il un moyen de forcer correctement le rechargement de certains modules? Ou mieux, empêcher d'une certaine manière Python de les mettre en cache?
J'ai essayé plusieurs approches, mais aucune ne fonctionne. En particulier, je rencontre des bugs vraiment très étranges, comme certains modules ou variables qui deviennent mystérieusement égaux à None
...
La seule ressource sensible que j'ai trouvée est Rechargement de modules Python , depuis pyunit, mais je ne l'ai pas vérifiée. Je voudrais quelque chose comme ça.
Une bonne alternative serait de redémarrer IPython ou de relancer l’interpréteur Python d’une manière ou d’une autre.
Donc, si vous développez en Python, quelle solution avez-vous trouvée à ce problème?
Modifier
Pour que tout soit clair: je comprends évidemment que certaines anciennes variables dépendant de l’état précédent du module peuvent rester inchangées. C'est bien pour moi. Pourquoi est-il si difficile en Python de forcer le rechargement d’un module sans que toutes sortes d’étranges erreurs ne se produisent?
Plus précisément, si j'ai tout mon module dans le fichier onemodule.py
, alors tout fonctionne comme prévu:
import sys
try:
del sys.modules['module']
except AttributeError:
pass
import module
obj = module.my_class()
Ce morceau de code fonctionne à merveille et je peux développer sans quitter IPython pendant des mois.
Cependant, chaque fois que mon module est composé de plusieurs sous-modules, l'enfer se déchaîne:
import os
for mod in ['module.submod1', 'module.submod2']:
try:
del sys.module[mod]
except AttributeError:
pass
# sometimes this works, sometimes not. WHY?
Pourquoi est-ce si différent pour Python si j'ai mon module dans un seul gros fichier ou dans plusieurs sous-modules? Pourquoi cette approche ne fonctionnerait-elle pas?
Quitter et redémarrer l'interpréteur est la meilleure solution. Toute sorte de stratégie de rechargement en direct ou de non-mise en cache ne fonctionnera pas de manière transparente, car des objets de modules qui ne sont plus existants peuvent exister et parce que les modules stockent parfois l’état et parce que même si votre cas d'utilisation permet réellement le rechargement à chaud, en vaut la peine.
import
vérifie si le module est en sys.modules
, et si c'est le cas, il le renvoie. Si vous souhaitez que l'importation charge le module fraîchement depuis le disque, vous pouvez commencer par supprimer la clé appropriée dans sys.modules
.
Il y a la fonction intégrée reload
qui va, à partir d'un objet module, le recharger à partir du disque et qui sera placée dans sys.modules
. Edit - en fait, il recompilera le code à partir du fichier sur le disque, puis le ré-évaluera dans le __dict__
du module existant. Quelque chose de potentiellement très différent que de créer un nouvel objet module.
Mike Graham a raison cependant; Bien recharger si vous avez même quelques objets vivants qui référencent le contenu du module que vous ne souhaitez plus, c'est difficile. Les objets existants continueront de référencer les classes à partir desquelles ils ont été instanciés est un problème évident, mais toutes les références créées à l'aide de from module import symbol
pointeront toujours sur l'objet de l'ancienne version du module. Beaucoup de choses faussement subtiles sont possibles.
Edit: Je suis d’accord avec le consensus selon lequel le redémarrage de l’interprète est de loin la solution la plus fiable. Mais pour des raisons de débogage, je suppose que vous pourriez essayer quelque chose comme ce qui suit. Je suis certain qu'il existe des cas de figure pour lesquels cela ne fonctionnerait pas, mais si vous ne faites rien de trop fou (sinon) avec le chargement de module dans votre paquet, cela pourrait être utile.
def reload_package(root_module):
package_name = root_module.__name__
# get a reference to each loaded module
loaded_package_modules = dict([
(key, value) for key, value in sys.modules.items()
if key.startswith(package_name) and isinstance(value, types.ModuleType)])
# delete references to these loaded modules from sys.modules
for key in loaded_package_modules:
del sys.modules[key]
# load each of the modules again;
# make old modules share state with new modules
for key in loaded_package_modules:
print 'loading %s' % key
newmodule = __import__(key)
oldmodule = loaded_package_modules[key]
oldmodule.__dict__.clear()
oldmodule.__dict__.update(newmodule.__dict__)
Ce que j'ai très brièvement testé comme suit:
import email, email.mime, email.mime.application
reload_package(email)
impression:
reloading email.iterators
reloading email.mime
reloading email.quoprimime
reloading email.encoders
reloading email.errors
reloading email
reloading email.charset
reloading email.mime.application
reloading email._parseaddr
reloading email.utils
reloading email.mime.base
reloading email.message
reloading email.mime.nonmultipart
reloading email.base64mime
Avec IPython, l'extension autoreload répète automatiquement une importation avant chaque appel de fonction. Cela fonctionne au moins dans les cas simples, mais ne comptez pas trop dessus: selon mon expérience, un redémarrage de l'interpréteur est toujours requis de temps en temps, en particulier lorsque des modifications de code ne se produisent que sur du code importé indirectement.
Exemple d'utilisation de la page liée:
In [1]: %load_ext autoreload
In [2]: %autoreload 2
In [3]: from foo import some_function
In [4]: some_function()
Out[4]: 42
In [5]: # open foo.py in an editor and change some_function to return 43
In [6]: some_function()
Out[6]: 43
Il y a déjà de très bonnes réponses ici, mais il est utile de connaître dreload, une fonction disponible dans IPython qui effectue le "rechargement complet". De la documentation:
Le module IPython.lib.deepreload vous permet de recharger récursivement un fichier module: les modifications apportées à l'une de ses dépendances seront rechargées sans avoir à sortir. Pour commencer à l'utiliser, faites:
http://ipython.org/ipython-doc/dev/interactive/reference.html#dreload
Il est disponible en tant que "global" dans le cahier IPython (au moins ma version, qui exécute la version 2.0).
HTH
J'utilise PythonNet dans mon projet. Heureusement, j'ai trouvé une commande qui peut parfaitement résoudre ce problème.
using (Py.GIL())
{
dynamic mod = Py.Import(this.moduleName);
if (mod == null)
throw new Exception( string.Format("Cannot find module {0}. Python script may not be complied successfully or module name is illegal.", this.moduleName));
// This command works perfect for me!
PythonEngine.ReloadModule(mod);
dynamic instance = mod.ClassName();
Vous pouvez utiliser le mécanisme d'importation de hook décrit dans PEP 302 pour charger non pas les modules eux-mêmes, mais une sorte d'objet proxy vous permettant de faire tout ce que vous voulez avec un objet module sous-jacent - rechargez-le, supprimez sa référence, etc.
L'avantage supplémentaire est que votre code existant ne nécessitera aucune modification et que cette fonctionnalité de module supplémentaire peut être arrachée à un seul point du code - où vous ajoutez réellement le Finder dans sys.meta_path
.
Quelques idées sur l'implémentation: créer un Finder qui acceptera de trouver n'importe quel module, à l'exception de builtin (vous n'avez rien à voir avec les modules internes), puis créez un chargeur qui renverra un objet proxy sous-classé à partir de types.ModuleType
au lieu d'un objet module réel. Notez que les objets du chargeur ne sont pas obligés de créer des références explicites aux modules chargés dans sys.modules
, mais cela est vivement recommandé car, comme vous l'avez déjà vu, il peut échouer de manière imprévisible. L’objet proxy doit capturer et transférer tous les __getattr__
, __setattr__
et __delattr__
au module réel sous-jacent auquel il conserve la référence. Vous n'aurez probablement pas besoin de définir __getattribute__
car vous ne cacheriez pas le contenu réel des modules avec vos méthodes proxy. Donc, vous devez maintenant communiquer avec le proxy d’une certaine manière - vous pouvez créer une méthode spéciale pour supprimer la référence sous-jacente, puis importer le module, extraire la référence du proxy renvoyé, supprimer le proxy et conserver la référence au module rechargé. Ouf, ça fait peur, mais ça devrait régler votre problème sans avoir à recharger Python à chaque fois.
Réfléchissez à deux fois pour arrêter et redémarrer en production
La solution facile sans quitter et redémarrer est en utilisant le rechargement de imp
import moduleA, moduleB
from imp import reload
reload (moduleB)