Come caricare dinamicamente una classe Python


167

Data una stringa di una classe Python, ad esempio my_package.my_module.MyClass, qual è il modo migliore per caricarla?

In altre parole, sto cercando un equivalente Class.forName()in Java, funzione in Python. Deve funzionare su Google App Engine.

Preferibilmente questa sarebbe una funzione che accetta l'FQN della classe come stringa e restituisce un riferimento alla classe:

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

Devo essere in grado di assegnare il riferimento di classe anche a una variabile.
pjesi,

1
Questo sembra essere un duplicato di: stackoverflow.com/questions/452969/...
cdleary

Hai ragione, è un duplicato, grazie per averlo trovato
pjesi,

1
Non ho mai avuto bisogno di caricare una classe come questa in Python. Sai dove si trova il modulo, quindi perché non caricare semplicemente il modulo e quindi utilizzare le sue classi come Python vuole, è molto più semplice
Adam Spence,

22
Se non hai mai avuto bisogno di farlo, non hai ancora scritto programmi abbastanza interessanti. Continuare a praticare.
John Tyree,

Risposte:


194

Dalla documentazione di Python, ecco la funzione desiderata:

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

Il motivo per cui un semplice __import__non funziona è perché qualsiasi importazione di qualcosa oltre il primo punto in una stringa di pacchetto è un attributo del modulo che stai importando. Pertanto, qualcosa del genere non funzionerà:

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

Dovresti chiamare la funzione sopra in questo modo:

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

O nel caso del tuo esempio:

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

EDIT : ero un po 'fuori su questo. Quello che sostanzialmente vuoi fare è questo:

from my_package.my_module import my_class

La funzione sopra è necessaria solo se hai un fromlist vuoto . Pertanto, la chiamata appropriata sarebbe così:

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

Ho provato my_import ('my_package.my_module.my_class') ma non ho trovato alcun modulo my_class, il che ha senso dato che è una classe e non un modulo. Tuttavia, se posso usare gettattr per ottenere la lezione dopo la chiamata a my_import
pjesi,

È strano. Tutto quanto oltre il primo punto viene chiamato usando getattr. Non ci dovrebbero essere differenze.
Jason Baker,

Grazie, penso che questo sia il modo migliore. Ora ho solo bisogno del modo migliore per dividere la stringa 'my_pakcage.my_module.my_class' in nome_mod, nome_klass ma immagino di poterlo capire :)
pjesi,

2
la documentazione di Python (sul codice) dell'importazione dice di usare importlib. così dovrebbe rispondere alla cassa di Adam Spence
naoko


125

Se non vuoi lanciarne uno tuo, c'è una funzione disponibile nel pydocmodulo che fa esattamente questo:

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

Il vantaggio di questo approccio rispetto agli altri elencati qui è che locatetroverà qualsiasi oggetto Python nel percorso punteggiato fornito, non solo un oggetto direttamente all'interno di un modulo. es my_package.my_module.MyClass.attr.

Se sei curioso di sapere qual è la loro ricetta, ecco la funzione:

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

Si basa sulla pydoc.safeimportfunzione. Ecco i documenti per questo:

"""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
Ho votato a favore di questa risposta. A proposito, ecco il codice che ha anche safeimport in quanto sembra strano importare pydoc solo per questo: github.com/python/cpython/blob/…
brianray

Ho votato anche questa risposta, questa è la migliore di tutte le risposte pertinenti.
Sunding Wei,

Questo sembra essere in grado di gestire qualnamecorrettamente (oggetto non in cima allo spazio dei nomi del modulo).
Mister Miyagi

sì, puoi farlolocate('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
una volta che hai importato il modulo in modo dinamico hai accesso alla classe tramite il modulo
Adam Spence,

Ho appena modificato la mia risposta per essere più conciso. Questo è il modo migliore per caricare una classe in Python.
Adam Spence,

BBB-Benny and the Spence! ;)
Ken

Grande! Fa anche parte della libreria standard da 2.7 in poi.
Apteryx,

Questo è il modo corretto di accedere a un modulo / classe. I documenti lo dichiarano qui: 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
Questa è la soluzione pulita! Puoi prendere in considerazione l'utilizzo di:(modulename, classname) = cl.rsplit('.', 2)
vdboor

È fantastico) Ho creato un pacchetto putils con diversi programmi di utilità, anche importando classi lì. Se vuoi, puoi usarlo da quel pacchetto.
Stan,

4
@vdboor rsplit('.', 1)?
Carles Barrobés,

Sono riuscito a passare {} invece di globali / locali e funziona ancora bene
valtron

17

Se stai usando Django puoi usarlo. Sì, sono consapevole che OP non ha chiesto django, ma ho incontrato questa domanda alla ricerca di una soluzione Django, non l'ho trovata e l'ho messa qui per il ragazzo / ragazza successivo che la cerca.

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

Tieni presente che se desideri importare qualcosa che non ha ., come reo argparseusare:

re = __import__('re')

5

Qui è per condividere qualcosa che ho trovato su __import__eimportlib mentre cercavo di risolvere questo problema.

Sto usando Python 3.7.3.

Quando provo ad arrivare alla classe dnel modulo a.b.c,

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

La modvariabile si riferisce allo spazio dei nomi principale a.

Quindi per arrivare in classe d, devo farlo

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

Se proviamo a fare

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

stiamo effettivamente cercando di cercare a.d .

Durante l'utilizzo importlib, suppongo che la libreria abbia fatto il ricorsivo getattrper noi. Quindi, quando usiamo importlib.import_module, otteniamo effettivamente una maniglia sul modulo più profondo.

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

1

OK, per me è così che ha funzionato (sto usando Python 2.7):

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

Quindi, b è un'istanza della classe 'MyClass'


0

Se ti capita di avere già un'istanza della classe desiderata, puoi utilizzare la funzione 'type' per estrarne il tipo di classe e utilizzarla per costruire una nuova istanza:

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
Si noti che questo funziona a causa di un bug nella funzione di importazione . I percorsi dei file non devono essere utilizzati nella funzione di importazione e non funzioneranno in Python 2.6 e versioni successive: docs.python.org/whatsnew/2.6.html#porting-to-python-2-6
Jason Baker

-2

In Google App Engine c'è una webapp2funzione chiamata import_string. Per maggiori informazioni vedi qui: https://webapp-improved.appspot.com/api/webapp2.html

Così,

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

Ad esempio, questo viene utilizzato webapp2.Routedove è possibile utilizzare un gestore o una stringa.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.