So laden Sie eine Python-Klasse dynamisch

167

my_package.my_module.MyClassWas ist bei einer Zeichenfolge einer Python-Klasse der beste Weg, um sie zu laden?

Mit anderen Worten, ich suche ein Äquivalent Class.forName()in Java, eine Funktion in Python. Es muss mit Google App Engine funktionieren.

Vorzugsweise ist dies eine Funktion, die den FQN der Klasse als Zeichenfolge akzeptiert und einen Verweis auf die Klasse zurückgibt:

my_class = load_class('my_package.my_module.MyClass')
my_instance = my_class()
pjesi
quelle
Ich muss in der Lage sein, die Klassenreferenz auch einer Variablen zuzuweisen.
pjesi
1
Dies scheint ein Duplikat von zu sein: stackoverflow.com/questions/452969/…
cdleary
Sie haben Recht, es ist ein Duplikat, danke, dass Sie es gefunden haben
pjesi
1
Ich musste noch nie eine solche Klasse in Python laden. Sie wissen, wo sich das Modul befindet. Warum also nicht einfach das Modul laden und dann seine Klassen verwenden, wie Python es möchte
Adam Spence,
22
Wenn Sie dies noch nie getan haben, haben Sie noch nicht genügend interessante Programme geschrieben. Übe weiter.
John Tyree

Antworten:

194

In der Python-Dokumentation finden Sie folgende Funktion:

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

Der Grund, warum ein einfaches __import__nicht funktioniert, ist, dass jeder Import von etwas nach dem ersten Punkt in einer Paketzeichenfolge ein Attribut des Moduls ist, das Sie importieren. So etwas funktioniert nicht:

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

Sie müssten die obige Funktion folgendermaßen aufrufen:

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

Oder im Fall Ihres Beispiels:

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

EDIT : Ich war ein bisschen daneben. Was Sie im Grunde tun möchten, ist Folgendes:

from my_package.my_module import my_class

Die obige Funktion ist nur erforderlich, wenn Sie eine leere fromlist haben. Der entsprechende Aufruf wäre also wie folgt:

mod = __import__('my_package.my_module', fromlist=['my_class'])
klass = getattr(mod, 'my_class')
Jason Baker
quelle
Ich habe my_import ('my_package.my_module.my_class') ausprobiert, aber kein Modul gefunden my_class, was sinnvoll ist, da es sich um eine Klasse und nicht um ein Modul handelt. Wie auch immer, wenn ich gettattr verwenden kann, um die Klasse nach dem Aufruf von my_import
pjesi
Das ist seltsam. Alles nach dem ersten Punkt wird mit getattr aufgerufen. Es sollte keinen Unterschied geben.
Jason Baker
Danke, ich denke das ist der beste Weg. Jetzt brauche ich nur noch den besten Weg, um die Zeichenfolge 'my_pakcage.my_module.my_class' in mod_name, klass_name aufzuteilen, aber ich denke, ich kann das herausfinden :)
pjesi
2
Die Python-Dokumentation (im Code) des Imports besagt, dass importlib verwendet werden soll.
Also
1
Link @naoko bezieht sich auf: docs.python.org/3/library/importlib.html#importlib.__import__
Noel Evans
125

Wenn Sie nicht selbst rollen möchten, steht im pydocModul eine Funktion zur Verfügung , die genau dies ausführt:

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

Der Vorteil dieses Ansatzes gegenüber den anderen hier aufgeführten ist , dass locatefinden alle an dem vorgesehenen gepunkteten Pfad Python - Objekt, nicht nur ein Objekt direkt innerhalb eines Moduls. zB my_package.my_module.MyClass.attr.

Wenn Sie neugierig sind, was ihr Rezept ist, ist hier die Funktion:

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

Es hängt von der pydoc.safeimportFunktion ab. Hier sind die Dokumente dafür:

"""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
quelle
1
Ich habe diese Antwort positiv bewertet. Übrigens, hier ist der Code, der auch safeimport hat, da es seltsam erscheint, pydoc nur dafür zu importieren: github.com/python/cpython/blob/…
brianray
Ich habe auch diese Antwort positiv bewertet, dies ist die beste aller relevanten Antworten.
Sunding Wei
Dies scheint auch in der Lage zu sein qualname(Objekt nicht oben im Modul-Namespace) korrekt zu handhaben .
MisterMiyagi
Ja, Sie können tunlocate('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
quelle
9
Sobald Sie das Modul dynamisch importiert haben, haben Sie über das Modul Zugriff auf die Klasse
Adam Spence
Ich habe gerade meine Antwort bearbeitet, um sie präziser zu gestalten. Dies ist der beste Weg, um eine Klasse in Python zu laden.
Adam Spence
BBB-Benny und die Spence! ;)
dKen
Toll! Es ist sogar Teil der Standardbibliothek ab 2.7.
Apteryx
Dies ist der richtige Weg, um auf ein Modul / eine Klasse zuzugreifen. In den Dokumenten wird dies hier angegeben: 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
quelle
5
Das ist die saubere Lösung! Sie könnten in Betracht ziehen:(modulename, classname) = cl.rsplit('.', 2)
vdboor
Es ist großartig) Ich hatte ein Putils-Paket mit verschiedenen Utils erstellt und dort auch Klassenimporte durchgeführt. Wenn Sie möchten, können Sie es aus diesem Paket verwenden.
Stan
4
@vdboor rsplit('.', 1)?
Carles Barrobés
Ich habe es geschafft, {} anstelle von Globals / Einheimischen zu übergeben und es funktioniert immer noch gut
Valtron
17

Wenn Sie Django verwenden, können Sie dies verwenden. Ja, ich bin mir bewusst, dass OP nicht nach Django gefragt hat, aber ich bin auf diese Frage gestoßen, um nach einer Django-Lösung zu suchen, habe keine gefunden und sie hier für den nächsten Jungen / das nächste Mädchen, das danach sucht, abgelegt.

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

Denken Sie daran, wenn Sie etwas importieren möchten, das keine hat ., wie reoder argparseverwendet:

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

Hier ist etwas zu teilen, was ich gefunden habe __import__und importlibwährend ich versucht habe, dieses Problem zu lösen.

Ich benutze Python 3.7.3.

Wenn ich versuche, zur Klasse dim Modul zu gelangen a.b.c,

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

Die modVariablen beziehen sich auf den oberen Namespace a.

Um in die Klasse zu kommen d, muss ich

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

Wenn wir es versuchen

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

wir versuchen tatsächlich zu suchen a.d.

Bei der Verwendung importlibhat die Bibliothek vermutlich die Rekursion getattrfür uns durchgeführt. Wenn wir also verwenden importlib.import_module, bekommen wir tatsächlich das tiefste Modul in den Griff.

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

OK, für mich hat das so funktioniert (ich benutze Python 2.7):

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

Dann ist b eine Instanz der Klasse 'MyClass'

lfvv
quelle
0

Wenn Sie bereits eine Instanz Ihrer gewünschten Klasse haben, können Sie mit der Funktion 'type' den Klassentyp extrahieren und damit eine neue Instanz erstellen:

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
quelle
-2
module = __import__("my_package/my_module")
the_class = getattr(module, "MyClass")
obj = the_class()
oefe
quelle
5
Beachten Sie, dass dies aufgrund eines Fehlers in der Importfunktion funktioniert. Dateipfade sollten nicht in der Importfunktion verwendet werden und funktionieren nicht in Python 2.6 und höher: docs.python.org/whatsnew/2.6.html#porting-to-python-2-6
Jason Baker
-2

In Google App Engine gibt es eine webapp2Funktion namens import_string. Weitere Informationen finden Sie hier: https://webapp-improved.appspot.com/api/webapp2.html

So,

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

Dies wird beispielsweise verwendet, webapp2.Routewenn Sie entweder einen Handler oder eine Zeichenfolge verwenden können.

ph0eb0s
quelle