J'écris une application Python qui prend comme commande un argument, par exemple:
$ python myapp.py command1
Je veux que l'application soit extensible, c'est-à-dire pouvoir ajouter de nouveaux modules qui implémentent de nouvelles commandes sans avoir à changer la source principale de l'application. L'arbre ressemble à quelque chose comme:
myapp/
__init__.py
commands/
__init__.py
command1.py
command2.py
foo.py
bar.py
Je souhaite donc que l'application trouve les modules de commande disponibles au moment de l'exécution et exécute celui qui convient.
Python définit une fonction _IMPORT_, qui prend une chaîne pour un nom de module:
__import __ (name, globals = None, locals = None, fromlist = (), level = 0)
La fonction importe le nom du module, en utilisant éventuellement les variables globales et locales indiquées pour déterminer comment interpréter le nom dans un contexte de package. La liste from donne les noms des objets ou des sous-modules à importer du module donné par nom.
Source: https://docs.python.org/3/library/functions.html#import
Donc actuellement j'ai quelque chose comme:
command = sys.argv[1]
try:
command_module = __import__("myapp.commands.%s" % command, fromlist=["myapp.commands"])
except ImportError:
# Display error message
command_module.run()
Cela fonctionne très bien, je me demande s'il existe peut-être un moyen plus idiomatique d'accomplir ce que nous faisons avec ce code.
Notez que je ne veux surtout pas utiliser d’œufs ou de points d’extension. Ce n'est pas un projet open-source et je ne m'attends pas à ce qu'il y ait des "plugins". Le but est de simplifier le code principal de l'application et de supprimer le besoin de le modifier chaque fois qu'un nouveau module de commande est ajouté.
Avec Python plus ancien que 2.7/3.1, c'est à peu près ce que vous faites.
Pour les versions plus récentes, voir importlib.import_module
pour Python 2 et Python 3 .
Vous pouvez aussi utiliser exec
si vous le souhaitez.
Ou en utilisant __import__
, vous pouvez importer une liste de modules en procédant comme suit:
>>> moduleNames = ['sys', 'os', 're', 'unittest']
>>> moduleNames
['sys', 'os', 're', 'unittest']
>>> modules = map(__import__, moduleNames)
Déchiré directement de Plonger dans Python .
La méthode recommandée pour Python 2.7 et 3.1 et versions ultérieures consiste à utiliser importlib
module:
importlib.import_module (name, package = None)
Importer un module. L'argument name spécifie le module à importer en termes absolus ou relatifs (par exemple, pkg.mod ou ..mod). Si le nom est spécifié en termes relatifs, l'argument de package doit être défini sur le nom du package qui doit servir d'ancre pour la résolution du nom du package (par exemple, import_module ('.. mod', 'pkg.subpkg') importera pkg.mod).
par exemple.
my_module = importlib.import_module('os.path')
Remarque: imp est obsolète depuis Python 3.4 au profit de importlib.
Comme mentionné précédemment, le module imp vous fournit les fonctions de chargement:
imp.load_source(name, path)
imp.load_compiled(name, path)
J'ai déjà utilisé ceux-ci pour effectuer quelque chose de similaire.
Dans mon cas, j'ai défini une classe spécifique avec les méthodes définies qui étaient requises . Une fois que j'ai chargé le module, je vérifiais si la classe était dans le module, puis créez une instance de cette classe.
import imp
import os
def load_from_file(filepath):
class_inst = None
expected_class = 'MyClass'
mod_name,file_ext = os.path.splitext(os.path.split(filepath)[-1])
if file_ext.lower() == '.py':
py_mod = imp.load_source(mod_name, filepath)
Elif file_ext.lower() == '.pyc':
py_mod = imp.load_compiled(mod_name, filepath)
if hasattr(py_mod, expected_class):
class_inst = getattr(py_mod, expected_class)()
return class_inst
Utilisez le module imp ou la fonction plus directe __import__()
.
Si vous le souhaitez dans vos locaux:
>>> mod = 'sys'
>>> locals()['my_module'] = __import__(mod)
>>> my_module.version
'2.6.6 (r266:84297, Aug 24 2010, 18:46:32) [MSC v.1500 32 bit (Intel)]'
idem fonctionnerait avec globals()
Vous pouvez utiliser exec
:
exec "import myapp.commands.%s" % command
Semblable à la solution @monkut mais réutilisable et tolérant aux erreurs, décrit ici http://stamat.wordpress.com/dynamic-module-import-in-python/ :
import os
import imp
def importFromURI(uri, absl):
mod = None
if not absl:
uri = os.path.normpath(os.path.join(os.path.dirname(__file__), uri))
path, fname = os.path.split(uri)
mname, ext = os.path.splitext(fname)
if os.path.exists(os.path.join(path,mname)+'.pyc'):
try:
return imp.load_compiled(mname, uri)
except:
pass
if os.path.exists(os.path.join(path,mname)+'.py'):
try:
return imp.load_source(mname, uri)
except:
pass
return mod
De nos jours, vous devriez utiliser importlib .
Les docs fournissent en fait une recette pour cela, et cela ressemble à:
import sys
import importlib.util
file_path = 'pluginX.py'
module_name = 'pluginX'
spec = importlib.util.spec_from_file_location(module_name, file_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
# check if it's all there..
def bla(mod):
print(dir(mod))
bla(module)
Importer un paquet ( par exemple , pluginX/__init__.py
) sous votre répertoire actuel est simple:
import importlib
pluginX = importlib.import_module('pluginX')
# check if it's all there..
def bla(mod):
print(dir(mod))
bla(module)
par exemple: mes noms de modules sont comme jan_module/feb_module/mar_module
mois = 'février'
exec 'from% s_module import *'% (month)
Cela ressemble à ce que vous voulez vraiment, c'est une architecture de plugin.
Vous devriez jeter un coup d'œil à la fonctionnalité entry points fournie par le paquet setuptools. Il offre un excellent moyen de découvrir les plug-ins chargés pour votre application.
La pièce ci-dessous a fonctionné pour moi:
>>>import imp;
>>>fp, pathname, description = imp.find_module("/home/test_module");
>>>test_module = imp.load_module("test_module", fp, pathname, description);
>>>print test_module.print_hello();
si vous voulez importer dans Shell-script:
python -c '<above entire code in one line>'