676 votes

Comment importer un module dont le nom est une chaîne de caractères ?

J'écris une application Python qui prend une commande comme argument, par exemple :

$ python myapp.py command1

Je souhaite que l'application soit extensible, c'est-à-dire qu'il soit possible d'ajouter de nouveaux modules qui mettent en œuvre de nouvelles commandes sans devoir modifier 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 veux donc que l'application trouve les modules de commande disponibles au moment de l'exécution et exécute le module approprié.

Python définit un __import__() qui prend une chaîne de caractères pour le nom du module :

__import__(name, globals=None, locals=None, fromlist=(), level=0)

La fonction importe le module name en utilisant potentiellement les globals y locals pour déterminer comment interpréter le nom dans un contexte de paquetage. L'adresse fromlist donne les noms des objets ou des sous-modules qui doivent être importés du module donné par name .

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, mais je me demande s'il n'y a pas une façon plus idiomatique d'accomplir ce que nous faisons avec ce code.

Notez que je ne veux surtout pas entrer dans l'utilisation des œufs ou des 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 de l'application principale et de supprimer la nécessité de le modifier à chaque fois qu'un nouveau module de commande est ajouté.

0 votes

À quoi sert l'option fromlist=["myapp.commands"] ?

2 votes

@PieterMüller : dans un shell python, tapez ceci : dir(__import__) . La fromlist doit être une liste de noms pour émuler "from name import ...".

395voto

Harley Holcombe Points 34618

Avec Python plus ancien que 2.7/3.1, c'est à peu près comme ça qu'il faut faire.

Pour les versions plus récentes, voir importlib.import_module pour Python 2 y Python 3 .

Vous pouvez utiliser exec si vous le souhaitez également.

Ou en utilisant __import__ vous pouvez importer une liste de modules en procédant ainsi :

>>> moduleNames = ['sys', 'os', 're', 'unittest'] 
>>> moduleNames
['sys', 'os', 're', 'unittest']
>>> modules = map(__import__, moduleNames)

Tiré directement de Plongez dans Python .

8 votes

Quelle est la différence avec l'exécution ?

1 votes

Comment pourriez-vous utiliser __init__ de ce module ?

10 votes

Un problème avec cette solution pour le PO est que le fait de piéger les exceptions pour un ou deux mauvais modules de "commande" fait que toute son application de commande échoue sur une exception. Personnellement, je ferais une boucle for sur chaque import enveloppé individuellement dans un try : mods=__import__() \nexcept ImportError comme error : report(error) pour permettre aux autres commandes de continuer à fonctionner pendant que les mauvaises sont corrigées.

389voto

Denis Malinovsky Points 841

La méthode recommandée pour Python 2.7 et 3.1 et plus est d'utiliser importlib module :

importlib.import_module(name, package=None)

Importez un module. L'argument nom 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 package doit être défini comme le nom du paquetage qui doit servir d'ancrage pour résoudre le nom du paquetage (par exemple, import_module('..mod', 'pkg.subpkg') importera pkg.mod).

par exemple

my_module = importlib.import_module('os.path')

7 votes

Recommandé par quelle source ou autorité ?

90 votes

La documentation conseille contre en utilisant __import__ en faveur du module mentionné ci-dessus.

9 votes

Cela fonctionne bien pour l'importation os.path Que diriez-vous de from os.path import * ?

135voto

monkut Points 14549

Nota: imp est déprécié depuis Python 3.4 en faveur de importlib

Comme mentionné, le imp vous fournit des fonctions de chargement :

imp.load_source(name, path)
imp.load_compiled(name, path)

Je les ai déjà utilisés pour réaliser quelque chose de similaire.

Dans mon cas, j'ai défini une classe spécifique avec des méthodes définies qui étaient nécessaires. Une fois que je chargeais le module, je vérifiais si la classe était dans le module, puis je créais une instance de cette classe, quelque chose comme ceci :

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

2 votes

Une solution simple et efficace. J'ai écrit une solution similaire : stamat.wordpress.com/dynamic-module-import-in-python Mais la vôtre a des défauts : Et les exceptions ? IOError et ImportError ? Pourquoi ne pas vérifier d'abord la version compilée et ensuite la version source. S'attendre à une classe réduit la réutilisabilité dans votre cas.

3 votes

Dans la ligne où vous construisez MyClass dans le module cible, vous ajoutez une référence redondante au nom de la classe. Il est déjà stocké dans expected_class pour que vous puissiez faire class_inst = getattr(py_mod,expected_name)() à la place.

1 votes

Notez que si un fichier compilé par octet correspondant (avec le suffixe .pyc ou .pyo) existe, il sera utilisé au lieu d'analyser le fichier source donné. . https://docs.python.org/2/library/imp.html#imp.load_source

18voto

gimel Points 30150

Utilisez le module imp ou le plus direct __import__() fonction.

6 votes

Ne pas utiliser __import__ voir Doc Python utiliser un importlib.import_module

12voto

Jonathan Points 11842

Si vous le voulez 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)]'

la même chose fonctionnerait avec globals()

3 votes

En documentation pour l'intégré locals() prévient explicitement que le "contenu de ce dictionnaire ne doit pas être modifié".

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X