web-dev-qa-db-fra.com

Comment charger dynamiquement une classe Python

Étant donné la chaîne d'une classe Python, par ex. my_package.my_module.MyClass, quel est le meilleur moyen de le charger?

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

De préférence, il s’agit d’une fonction qui accepte le FQN de la classe en tant que chaîne et renvoie une référence à la classe:

my_class = load_class('my_package.my_module.MyClass')
my_instance = my_class()
122
pjesi

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 au-delà du premier point d'une chaîne de package est un attribut du module que vous importez. Ainsi, quelque chose comme ceci ne fonctionnera pas:

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

Vous devez appeler la fonction ci-dessus comme suit:

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 désemparé. Ce que vous voulez fondamentalement faire est la suivante:

from my_package.my_module import my_class

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

mod = __import__('my_package.my_module', fromlist=['my_class'])
klass = getattr(mod, 'my_class')
162
Jason Baker

Si vous ne voulez pas lancer le vôtre, il existe une fonction disponible dans le module pydoc 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 options répertoriées ici est que locate trouvera un objet n’importe quel python dans le chemin en pointillé fourni, et pas seulement un objet directement dans un module. par exemple. my_package.my_module.MyClass.attr.

Si vous êtes curieux de connaître 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 s'appuie sur la fonction pydoc.safeimport. 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)."""
93
chadrik
import importlib

module = importlib.import_module('my_package.my_module')
my_class = getattr(module, 'MyClass')
my_instance = my_class()
68
Adam Spence
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)
28
Stan

Si vous utilisez Django, vous pouvez l'utiliser. Oui, je suis conscient que 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')

N'oubliez pas que si vous souhaitez importer quelque chose qui n'a pas de ., tel que re ou argparse, utilisez:

re = __import__('re')
2
Javier Buzzi

OK, pour moi c'est comme cela que ça a fonctionné (j'utilise Python 2.7):

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

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

1
lfvv

Si vous possédez déjà une instance de votre classe souhaitée, vous pouvez utiliser la fonction 'type' pour extraire son type de classe et l'utiliser pour créer 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()
0
Jason DeMorrow

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

J'utilise Python 3.7.3.

Quand j'essaie d'obtenir la classe d dans le module a.b.c,

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

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

Donc, pour arriver à la 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.

En utilisant importlib, je suppose que la bibliothèque a fait la récursive getattr pour 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
0
Richard Wong