Importar dinámicamente un método en un archivo, desde una cadena

84

Tengo una cadena, por ejemplo: abc.def.ghi.jkl.myfile.mymethod. ¿Cómo importo dinámicamente mymethod?

Así es como lo hice:

def get_method_from_file(full_path):
    if len(full_path) == 1:
        return map(__import__,[full_path[0]])[0]
    return getattr(get_method_from_file(full_path[:-1]),full_path[-1])


if __name__=='__main__':
    print get_method_from_file('abc.def.ghi.jkl.myfile.mymethod'.split('.'))

Me pregunto si se requiere la importación de módulos individuales.

Editar: estoy usando Python versión 2.6.5.

Lakshman Prasad
fuente

Respuestas:

111

Desde Python 2.7 puede usar la función importlib.import_module () . Puede importar un módulo y acceder a un objeto definido en él con el siguiente código:

from importlib import import_module

p, m = name.rsplit('.', 1)

mod = import_module(p)
met = getattr(mod, m)

met()
frm
fuente
2
Vale la pena señalar que los documentos de Python recomiendan usar importlib.import_module()over __import__(): docs.python.org/2/library/functions.html#__import__ - para 2.7+.
BoltzmannBrain
4
import_module + rsplit = El único camino verdadero.
Cecil Curry
32

No es necesario importar los módulos individuales. Basta con importar el módulo del que desea importar un nombre y proporcionar el fromlistargumento:

def import_from(module, name):
    module = __import__(module, fromlist=[name])
    return getattr(module, name)

Para su ejemplo abc.def.ghi.jkl.myfile.mymethod, llame a esta función como

import_from("abc.def.ghi.jkl.myfile", "mymethod")

(Tenga en cuenta que las funciones a nivel de módulo se denominan funciones en Python, no métodos).

Para una tarea tan simple, no hay ninguna ventaja en usar el importlibmódulo.

Sven Marnach
fuente
import_from () me apuntó en la dirección correcta. myClass = getattr(__import__("module.to.import", fromlist=["myClassName"]), "myClassName"). ¡Gracias por la ayuda!
Fydo
1
El __import__enfoque funciona. Pero intenta correr help(__import__). Dice "Debido a que esta función está pensada para que la utilice el intérprete de Python y no para un uso general, es mejor utilizarla importlib.import_module()para importar un módulo mediante programación".
twasbrillig
5
@twasbrillig: Cuando respondí esta pregunta, Python 2.5 y 2.6 todavía se usaban ampliamente. Hoy, ciertamente es mejor usarlo importliben su lugar.
Sven Marnach
1
Genial, gracias. Podría ser una buena idea actualizar la respuesta para reflejar esto.
twasbrillig
26

Para Python <2.7, se puede usar el método incorporado __ import__ :

__import__('abc.def.ghi.jkl.myfile.mymethod', fromlist=[''])

Para Python> = 2.7 o 3.1 se ha agregado el conveniente método importlib.import_module . Simplemente importe su módulo así:

importlib.import_module('abc.def.ghi.jkl.myfile.mymethod')

Actualización : versión actualizada de acuerdo con los comentarios ( debo admitir que no leí la cadena a importar hasta el final y me perdí el hecho de que se debe importar un método de un módulo y no un módulo en sí) :

Python <2.7:

mymethod = getattr(__import__("abc.def.ghi.jkl.myfile", fromlist=["mymethod"]))

Python> = 2.7:

mymethod = getattr(importlib.import_module("abc.def.ghi.jkl.myfile"), "mymethod")
gecco
fuente
6
Ninguna de estas soluciones funcionará porque el primer parámetro debe ser un nombre de módulo en ambos __import__()y importlib.import_module().
Kris Hardy
1
importlib.import_module ('abc.def.ghi.jkl.myfile.mymethod') no funcionará porque tiene que especificar "qué módulo importar en términos absolutos o relativos" y no el nombre "calificado" del método.
viernes
Esto no funciona, obviamente, ya que el primer parámetro a importar debería ser un módulo, no una cadena.
Lakshman Prasad
11

No está claro qué está intentando hacer con su espacio de nombres local. Supongo que lo quieres solo my_methodcomo local, escribiendo output = my_method()?

# This is equivalent to "from a.b.myfile import my_method"
the_module = importlib.import_module("a.b.myfile")
same_module = __import__("a.b.myfile")
# import_module() and __input__() only return modules
my_method = getattr(the_module, "my_method")

# or, more concisely,
my_method = getattr(__import__("a.b.myfile"), "my_method")
output = my_method()

Si bien solo agrega my_methodal espacio de nombres local, carga la cadena de módulos. Puede ver los cambios observando las claves de sys.modulesantes y después de la importación. Espero que esto sea más claro y preciso que sus otras respuestas.

Para completar, así es como agrega toda la cadena.

# This is equivalent to "import a.b.myfile"
a = __import__("a.b.myfile")
also_a = importlib.import_module("a.b.myfile")
output = a.b.myfile.my_method()

# This is equivalent to "from a.b import myfile"
myfile = __import__("a.b.myfile", fromlist="a.b")
also_myfile = importlib.import_module("a.b.myfile", "a.b")
output = myfile.my_method()

Y, finalmente, si está usando __import__()y ha modificado su ruta de búsqueda después de que se inició el programa, es posible que deba usar __import__(normal args, globals=globals(), locals=locals()). El por qué es una discusión compleja.

Charles Merriam
fuente
Su primer ejemplo de the_moduley same_moduleestá mal y produce resultados diferentes. Votar en contra.
sleepycal
7
from importlib import import_module

name = "file.py".strip('.py')
# if Path like : "path/python/file.py" 
# use name.replaces("/",".")

imp = import_module(name)

# get Class From File.py
model = getattr(imp, "classNameImportFromFile")

NClass = model() # Class From file 
Alaa Akiel
fuente
2
Gracias por la respuesta, solo un pequeño problema: no creo que se .strip()comporte correctamente en el caso que dijiste. Específicamente, si el archivo comienza con "py", esos caracteres también se eliminarán. Por ejemplo, "pyfile.py".strip(".py")produce"file" , donde sería preferible que produjera "pyfile"en este caso. name.replace(".py","")funciona bien, sin embargo.
jxmorris12
1

Este sitio web tiene una buena solución: load_class . Lo uso así:

foo = load_class(package.subpackage.FooClass)()
type(foo) # returns FooClass

Según lo solicitado, aquí está el código del enlace web:

import importlib

def load_class(full_class_string):
    """
    dynamically load a class from a string
    """

    class_data = full_class_string.split(".")
    module_path = ".".join(class_data[:-1])
    class_str = class_data[-1]

    module = importlib.import_module(module_path)
    # Finally, we retrieve the Class
    return getattr(module, class_str)
Kingaj
fuente
Incluya el resumen / código del enlace proporcionado. Las respuestas de solo enlace no son buenas porque tienden a deteriorarse.
Otro usuario más
0

Utilice importlib(solo 2.7+).

Daniel Roseman
fuente
1
Yo uso 2.6.5. Puedo hacer from __future__algo?
Lakshman Prasad
5
No, __future__es para funciones de idioma, no para nuevos módulos stdlib.
ThiefMaster
1
Utilice el incorporado __import__como en los ejemplos anteriores. Funciona de nuevo a 2.5 y, sin palabras clave, antes.
Charles Merriam
0

La forma en que tiendo a esto (así como a otras bibliotecas, como pilones y pegar, si mi memoria no me falla) es separar el nombre del módulo del nombre de la función / atributo usando un ':' entre ellos . Vea el siguiente ejemplo:

'abc.def.ghi.jkl.myfile:mymethod'

Esto hace que la import_from(path)función siguiente sea un poco más fácil de usar.

def import_from(path):
    """
    Import an attribute, function or class from a module.
    :attr path: A path descriptor in the form of 'pkg.module.submodule:attribute'
    :type path: str
    """
    path_parts = path.split(':')
    if len(path_parts) < 2:
        raise ImportError("path must be in the form of pkg.module.submodule:attribute")
    module = __import__(path_parts[0], fromlist=path_parts[1])
    return getattr(module, path_parts[1])


if __name__=='__main__':
    func = import_from('a.b.c.d.myfile:mymethod')
    func()
Kris Hardy
fuente
1
¿Por qué debería alguien hacer esto? Esto requiere que la persona que llama combine el nombre del módulo y el nombre del atributo con dos puntos, y la función debe dividirse nuevamente en los dos puntos. ¿Por qué no utilizar simplemente dos parámetros en primer lugar?
Sven Marnach
1
A menudo, una situación en la que esto ocurrirá es si transfiere devoluciones de llamada desde un marco a su aplicación utilizando archivos de configuración (.yaml, .ini, etc.). Luego, puede especificar qué función debe llamar el marco, etc., con una sola cadena en su archivo de configuración.
Kris Hardy
Bueno, sí, es posible que desee elegir esto como sintaxis de archivo de configuración. Pero, ¿cómo se relaciona esto con la pregunta del OP? (E incluso si hubiera elegido esto como la sintaxis de mi archivo de configuración, preferiría que mi analizador de archivos de configuración lo analice, no mediante funciones de API aleatorias).
Sven Marnach
-1

Qué tal esto :

def import_module(name):

    mod = __import__(name)
    for s in name.split('.')[1:]:
        mod = getattr(mod, s)
    return mod
Aymen Alsaadi
fuente
Se desaconseja el uso del método mágico. En su lugar, utilice importlib.
dephinera
¿Puede explicar por qué es desalentador? @dephinera
Aymen Alsaadi
Bueno, porque los métodos mágicos están destinados a ser llamados por el intérprete, no explícitamente por nuestro código. Al llamarlos explícitamente, confiamos en que su firma nunca cambiará, sin embargo, podría cambiar en futuras versiones de idioma.
dephinera