Comment charger dynamiquement une classe Python

167

Étant donné une chaîne d'une classe Python, par exemple my_package.my_module.MyClass, quelle est la meilleure façon possible de la charger?

En d'autres termes, je recherche un équivalent Class.forName()en Java, une fonction en Python. Il doit fonctionner sur Google App Engine.

De préférence, ce serait une fonction qui accepte le FQN de la classe sous forme de chaîne et renvoie une référence à la classe:

my_class = load_class('my_package.my_module.MyClass')
my_instance = my_class()
pjesi
la source
Je dois également pouvoir attribuer la référence de classe à une variable.
pjesi le
1
Cela semble être un double de: stackoverflow.com/questions/452969/…
cdleary
Vous avez raison c'est un doublon, merci de l'avoir trouvé
pjesi
1
Je n'ai jamais eu besoin de charger une classe comme celle-ci en Python. Vous savez où se trouve le module, alors pourquoi ne pas simplement charger le module et ensuite utiliser ses classes comme Python le veut, c'est beaucoup plus simple
Adam Spence
22
Si vous n'avez jamais eu besoin de faire cela, vous n'avez pas encore écrit de programmes assez intéressants. Continuez à pratiquer.
John Tyree

Réponses:

194

Dans la documentation python, voici la fonction que vous souhaitez:

def my_import(name):
    components = name.split('.')
    mod = __import__(components[0])
    for comp in components[1:]:
        mod = getattr(mod, comp)
    return mod

La raison pour laquelle un simple __import__ne fonctionnera pas est que toute importation de tout ce qui se trouve après le premier point d'une chaîne de package est un attribut du module que vous importez. Ainsi, quelque chose comme ça ne fonctionnera pas:

__import__('foo.bar.baz.qux')

Vous devrez appeler la fonction ci-dessus comme ceci:

my_import('foo.bar.baz.qux')

Ou dans le cas de votre exemple:

klass = my_import('my_package.my_module.my_class')
some_object = klass()

EDIT : J'étais un peu hors de ça. Ce que vous voulez faire, c'est ceci:

from my_package.my_module import my_class

La fonction ci-dessus n'est nécessaire que si vous avez une liste vide . Ainsi, l'appel approprié serait comme ceci:

mod = __import__('my_package.my_module', fromlist=['my_class'])
klass = getattr(mod, 'my_class')
Jason Baker
la source
J'ai essayé my_import ('my_package.my_module.my_class') mais je n'ai pas trouvé de module my_class, ce qui est logique car c'est une classe et non un module. Cependant, si je peux utiliser gettattr pour obtenir la classe après l'appel à my_import
pjesi
C'est étrange. Tout ce qui dépasse le premier point est appelé en utilisant getattr. Il ne devrait y avoir aucune différence.
Jason Baker
Merci, je pense que c'est la meilleure façon. Maintenant, je n'ai besoin que du meilleur moyen de diviser la chaîne 'my_pakcage.my_module.my_class' en mod_name, klass_name mais je suppose que je peux comprendre cela :)
pjesi
2
la documentation python (sur le code) de l' importation dit d'utiliser importlib. donc devrait vérifier la réponse par Adam Spence
naoko
1
Le lien @naoko fait référence à: docs.python.org/3/library/importlib.html#importlib.__import__
Noel Evans
125

Si vous ne voulez pas rouler vous-même, il existe une fonction disponible dans le pydocmodule qui fait exactement cela:

from pydoc import locate
my_class = locate('my_package.my_module.MyClass')

L'avantage de cette approche par rapport aux autres listées ici est qu'elle locatetrouvera n'importe quel objet Python dans le chemin en pointillé fourni, pas seulement un objet directement dans un module. par exemple my_package.my_module.MyClass.attr.

Si vous êtes curieux de savoir quelle est leur recette, voici la fonction:

def locate(path, forceload=0):
    """Locate an object by name or dotted path, importing as necessary."""
    parts = [part for part in split(path, '.') if part]
    module, n = None, 0
    while n < len(parts):
        nextmodule = safeimport(join(parts[:n+1], '.'), forceload)
        if nextmodule: module, n = nextmodule, n + 1
        else: break
    if module:
        object = module
    else:
        object = __builtin__
    for part in parts[n:]:
        try:
            object = getattr(object, part)
        except AttributeError:
            return None
    return object

Il repose sur la pydoc.safeimportfonction. Voici la documentation pour cela:

"""Import a module; handle errors; return None if the module isn't found.

If the module *is* found but an exception occurs, it's wrapped in an
ErrorDuringImport exception and reraised.  Unlike __import__, if a
package path is specified, the module at the end of the path is returned,
not the package at the beginning.  If the optional 'forceload' argument
is 1, we reload the module from disk (unless it's a dynamic extension)."""
Chadrik
la source
1
J'ai voté pour cette réponse. BTW, voici le code qui a également safeimport car il semble étrange d'importer pydoc juste pour cela: github.com/python/cpython/blob
...
J'ai également voté pour cette réponse, c'est la meilleure de toutes les réponses pertinentes.
Sunding Wei
Cela semble également pouvoir gérer qualnamecorrectement (l'objet qui n'est pas en haut de l'espace de noms du module).
MisterMiyagi
oui, vous pouvez le fairelocate('my_package.my_module.MyClass.attr.method.etc')
chadrik
109
import importlib

module = importlib.import_module('my_package.my_module')
my_class = getattr(module, 'MyClass')
my_instance = my_class()
Adam Spence
la source
9
une fois que vous avez importé le module dynamiquement, vous avez accès à la classe via le module
Adam Spence
Je viens de modifier ma réponse pour être plus concise. C'est la meilleure façon de charger une classe en Python.
Adam Spence
BBB-Benny et le Spence! ;)
dKen
Génial! Cela fait même partie de la bibliothèque standard de 2.7 et plus.
Apteryx
C'est la bonne façon d'accéder à un module / une classe. La documentation indique ceci ici: docs.python.org/3/library/importlib.html#importlib.__import__
Noel Evans
29
def import_class(cl):
    d = cl.rfind(".")
    classname = cl[d+1:len(cl)]
    m = __import__(cl[0:d], globals(), locals(), [classname])
    return getattr(m, classname)
Stan
la source
5
C'est la solution propre! Vous pouvez envisager d'utiliser:(modulename, classname) = cl.rsplit('.', 2)
vdboor
C'est génial) J'avais créé un package putils avec différents utils, y compris l'importation de classes. Si vous le souhaitez, vous pouvez l'utiliser à partir de ce package.
Stan
4
@vdboor rsplit('.', 1)?
Carles Barrobés
J'ai réussi à passer {} au lieu de globals / locaux et cela fonctionne toujours bien
valtron
18

Si vous utilisez Django, vous pouvez l'utiliser. Oui, je sais qu'OP n'a pas demandé django, mais j'ai rencontré cette question à la recherche d'une solution Django, je n'en ai pas trouvé et je l'ai mise ici pour le prochain garçon / fille qui la recherche.

# It's available for v1.7+
# https://github.com/django/django/blob/stable/1.7.x/django/utils/module_loading.py
from django.utils.module_loading import import_string

Klass = import_string('path.to.module.Klass')
func = import_string('path.to.module.func')
var = import_string('path.to.module.var')

Gardez à l'esprit que si vous souhaitez importer quelque chose qui n'a pas ., comme reou argparsen'utilise pas:

re = __import__('re')
Javier Buzzi
la source
6

Voici pour partager quelque chose que j'ai trouvé sur __import__etimportlib en essayant de résoudre ce problème.

J'utilise Python 3.7.3.

Quand j'essaye d'accéder à la classe ddans le module a.b.c,

mod = __import__('a.b.c')

La modvariable fait référence à l'espace de noms supérieur a.

Alors pour aller en classe d, je dois

mod = getattr(mod, 'b') #mod is now module b
mod = getattr(mod, 'c') #mod is now module c
mod = getattr(mod, 'd') #mod is now class d

Si nous essayons de faire

mod = __import__('a.b.c')
d = getattr(mod, 'd')

nous essayons en fait de rechercher a.d.

Lors de l'utilisation importlib, je suppose que la bibliothèque a fait le récursif getattrpour nous. Ainsi, lorsque nous utilisons importlib.import_module, nous obtenons en fait une poignée sur le module le plus profond.

mod = importlib.import_module('a.b.c') #mod is module c
d = getattr(mod, 'd') #this is a.b.c.d
Richard Wong
la source
1

OK, pour moi, c'est ainsi que cela fonctionnait (j'utilise Python 2.7):

a = __import__('file_to_import', globals(), locals(), ['*'], -1)
b = a.MyClass()

Ensuite, b est une instance de la classe 'MyClass'

lfvv
la source
0

Si vous avez déjà une instance de la classe souhaitée, vous pouvez utiliser la fonction 'type' pour extraire son type de classe et l'utiliser pour construire une nouvelle instance:

class Something(object):
    def __init__(self, name):
        self.name = name
    def display(self):
        print(self.name)

one = Something("one")
one.display()
cls = type(one)
two = cls("two")
two.display()
Jason DeMorrow
la source
-2
module = __import__("my_package/my_module")
the_class = getattr(module, "MyClass")
obj = the_class()
oefe
la source
5
Notez que cela fonctionne à cause d'un bogue dans la fonction d' importation . Les chemins de fichiers ne doivent pas être utilisés dans la fonction d' importation et ne fonctionneront pas dans python 2.6 et supérieur: docs.python.org/whatsnew/2.6.html#porting-to-python-2-6
Jason Baker
-2

Dans Google App Engine, il existe une webapp2fonction appelée import_string. Pour plus d'informations, voir ici: https://webapp-improved.appspot.com/api/webapp2.html

Alors,

import webapp2
my_class = webapp2.import_string('my_package.my_module.MyClass')

Par exemple, cela est utilisé dans le webapp2.Routeoù vous pouvez utiliser un gestionnaire ou une chaîne.

ph0eb0s
la source