Cómo cargar dinámicamente una clase de Python

167

Dada una cadena de una clase de Python, por ejemplo my_package.my_module.MyClass , ¿cuál es la mejor manera posible de cargarla?

En otras palabras, estoy buscando un equivalente Class.forName() en Java, función en Python. Tiene que funcionar en Google App Engine.

Preferiblemente, esta sería una función que acepta el FQN de la clase como una cadena y devuelve una referencia a la clase:

my_class = load_class('my_package.my_module.MyClass')
my_instance = my_class()
pjesi
fuente
Necesito poder asignar la referencia de clase a una variable también.
pjesi
1
Esto parece ser un duplicado de: stackoverflow.com/questions/452969/…
cdleary
Tienes razón, es un duplicado, gracias por encontrarlo
pjesi
1
Nunca he necesitado cargar una clase como esta en Python. Sabes dónde está el módulo, entonces ¿por qué no solo cargar el módulo y luego usar sus clases como Python quiere, es mucho más simple
Adam Spence
22
Si nunca ha necesitado hacer esto, entonces todavía no ha escrito programas lo suficientemente interesantes. Sigue practicando.
John Tyree

Respuestas:

194

De la documentación de Python, aquí está la función que desea:

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

La razón por la que un simple __import__no funcionará es porque cualquier importación de algo más allá del primer punto en una cadena de paquete es un atributo del módulo que está importando. Por lo tanto, algo como esto no funcionará:

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

Tendría que llamar a la función anterior de esta manera:

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

O en el caso de tu ejemplo:

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

EDITAR : Estaba un poco fuera de lugar. Lo que básicamente quieres hacer es esto:

from my_package.my_module import my_class

La función anterior solo es necesaria si tiene una lista vacía . Por lo tanto, la llamada apropiada sería así:

mod = __import__('my_package.my_module', fromlist=['my_class'])
klass = getattr(mod, 'my_class')
Jason Baker
fuente
Intenté my_import ('my_package.my_module.my_class') pero no encontré ningún módulo my_class, lo cual tiene sentido ya que es una clase, no un módulo. Sin embargo, si puedo usar gettattr para obtener la clase después de la llamada a my_import
pjesi
Eso es extraño. Todo lo que pasa del primer punto se llama usando getattr. No debería haber ninguna diferencia.
Jason Baker
Gracias, creo que esta es la mejor manera. Ahora solo me falta la mejor manera de dividir la cadena 'my_pakcage.my_module.my_class' en mod_name, klass_name pero supongo que puedo imaginar eso :)
pjesi
2
La documentación de Python (en el código) de importación dice que use importlib. entonces debería pagar la respuesta de Adam Spence
naoko
1
Link @naoko se refiere a: docs.python.org/3/library/importlib.html#importlib.__import__
Noel Evans
125

Si no quiere rodar el suyo, hay una función disponible en el pydocmódulo que hace exactamente esto:

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

La ventaja de este enfoque sobre los otros enumerados aquí es que locateencontrará cualquier objeto de Python en la ruta de puntos proporcionada, no solo un objeto directamente dentro de un módulo. por ej my_package.my_module.MyClass.attr.

Si tienes curiosidad por cuál es su receta, aquí está la función:

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

Se basa en la pydoc.safeimportfunción. Aquí están los documentos para eso:

"""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
fuente
1
Voté esta respuesta. Por cierto, aquí está el código que también tiene importación segura, ya que parece extraño importar pydoc solo por esto: github.com/python/cpython/blob/…
brianray
También voté por esta respuesta, esta es la mejor de todas las respuestas relevantes.
Sunding Wei
Esto parece ser capaz de manejar qualname(el objeto no está en la parte superior del espacio de nombres del módulo) correctamente también.
MisterMiyagi
sí, puedes hacerlocate('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
fuente
9
una vez que haya importado el módulo dinámicamente, tendrá acceso a la clase a través del módulo
Adam Spence
Acabo de editar mi respuesta para ser más conciso. Esta es la mejor manera de cargar una clase en Python.
Adam Spence
¡BBB-Benny y el Spence! ;)
dKen
¡Excelente! Incluso es parte de la biblioteca estándar de 2.7 en adelante.
Apteryx
Esta es la forma correcta de acceder a un módulo / clase. Los documentos indican esto aquí: 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
fuente
55
Esta es la solución limpia! Podría considerar usar:(modulename, classname) = cl.rsplit('.', 2)
vdboor
Es genial) Creé el paquete putils con diferentes utilidades, también importando clases allí. Si lo desea, puede usarlo desde ese paquete.
Stan
44
@vdboor rsplit('.', 1)?
Carles Barrobés
Logré
17

Si está usando Django, puede usar esto. Sí, sé que OP no solicitó django, pero me encontré con esta pregunta buscando una solución de Django, no encontré una y la puse aquí para el próximo chico / chica que la busca.

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

Tenga en cuenta que si desea importar algo que no tiene un me .gusta reo argparseuso:

re = __import__('re')
Javier Buzzi
fuente
5

Aquí es para compartir algo que encontré __import__yimportlib al intentar resolver este problema.

Estoy usando Python 3.7.3.

Cuando trato de llegar a la clase den el módulo a.b.c,

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

La modvariable se refiere al espacio de nombres superior a.

Entonces, para llegar a la clase d, necesito

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 tratamos de hacer

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

en realidad estamos tratando de buscar a.d .

Al usar importlib, supongo que la biblioteca ha hecho el recursivo getattrpor nosotros. Entonces, cuando usamos importlib.import_module, en realidad obtenemos el módulo más profundo.

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

OK, para mí esa es la forma en que funcionó (estoy usando Python 2.7):

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

Entonces, b es una instancia de la clase 'MyClass'

lfvv
fuente
0

Si ya tiene una instancia de su clase deseada, puede usar la función 'tipo' para extraer su tipo de clase y usarla para construir una nueva instancia:

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
fuente
-2
module = __import__("my_package/my_module")
the_class = getattr(module, "MyClass")
obj = the_class()
oefe
fuente
55
Tenga en cuenta que esto funciona debido a un error en la función de importación . Las rutas de archivo no deben usarse en la función de importación y no funcionarán en python 2.6 y superior: docs.python.org/whatsnew/2.6.html#porting-to-python-2-6
Jason Baker
-2

En Google App Engine hay una webapp2función llamada import_string. Para obtener más información, consulte aquí: https://webapp-improved.appspot.com/api/webapp2.html

Entonces,

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

Por ejemplo, esto se usa en el lugar webapp2.Routedonde puede usar un controlador o una cadena.

ph0eb0s
fuente