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()

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')

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


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)."""

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()

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)

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')

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

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'


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()

-2
module = __import__("my_package/my_module")
the_class = getattr(module, "MyClass")
obj = the_class()

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.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.