Jak dynamicznie ładować klasę Pythona

167

Biorąc pod uwagę ciąg klasy Pythona, np. my_package.my_module.MyClassJaki jest najlepszy możliwy sposób jej załadowania?

Innymi słowy szukam odpowiednika Class.forName()w Javie, funkcji w Pythonie. Musi działać w Google App Engine.

Najlepiej byłoby, gdyby była to funkcja, która akceptuje FQN klasy jako ciąg i zwraca odniesienie do klasy:

my_class = load_class('my_package.my_module.MyClass')
my_instance = my_class()
pjesi
źródło
Muszę mieć możliwość przypisania odwołania do klasy również zmiennej.
pjesi
1
Wygląda na to, że jest to duplikat: stackoverflow.com/questions/452969/…
cdleary
Masz rację, to duplikat, dzięki za znalezienie go
pjesi,
1
Nigdy nie musiałem ładować takiej klasy w Pythonie. Wiesz, gdzie jest moduł, więc dlaczego nie po prostu załadować modułu, a następnie użyć jego klas, takich jak Python, jest to znacznie prostsze
Adam Spence
22
Jeśli nigdy nie musiałeś tego robić, to nie napisałeś jeszcze wystarczająco interesujących programów. Ćwicz dalej.
John Tyree,

Odpowiedzi:

194

Z dokumentacji Pythona, oto funkcja, której potrzebujesz:

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

Powodem, dla którego proste __import__nie zadziała, jest to, że jakikolwiek import czegokolwiek poza pierwszą kropką w ciągu pakietu jest atrybutem importowanego modułu. Tak więc coś takiego nie zadziała:

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

Musiałbyś wywołać powyższą funkcję w ten sposób:

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

Lub w Twoim przykładzie:

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

EDYCJA : Trochę mnie to obchodziło. Zasadniczo chcesz to zrobić:

from my_package.my_module import my_class

Powyższa funkcja jest konieczna tylko wtedy, gdy masz pustą listę fromlist. Zatem odpowiednie wywołanie wyglądałoby tak:

mod = __import__('my_package.my_module', fromlist=['my_class'])
klass = getattr(mod, 'my_class')
Jason Baker
źródło
Próbowałem my_import („my_package.my_module.my_class”), ale nie udało mi się znaleźć modułu my_class, co ma sens, ponieważ jest to klasa, a nie moduł. Ale jeśli mogę użyć gettattr, aby uzyskać klasę po wywołaniu my_import
pjesi
To dziwne. Wszystko poza pierwszą kropką jest wywoływane za pomocą getattr. Nie powinno być żadnej różnicy.
Jason Baker
Dzięki, myślę, że to najlepszy sposób. Teraz potrzebuję tylko najlepszego sposobu, aby podzielić ciąg „my_pakcage.my_module.my_class” na mod_name, klass_name, ale myślę, że mogę to
rozgryźć
2
Dokumentacja Pythona (w kodzie) importu mówi, że należy używać importlib. tak powinienem
sprawdzić
1
Link @naoko odnosi się do: docs.python.org/3/library/importlib.html#importlib.__import__
Noel Evans,
125

Jeśli nie chcesz tworzyć własnych, w pydocmodule dostępna jest funkcja , która robi dokładnie to:

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

Zaletą tego podejścia w porównaniu z innymi wymienionymi tutaj jest to, że locateznajdzie dowolny obiekt Pythona w podanej kropkowanej ścieżce, a nie tylko obiekt bezpośrednio w module. np my_package.my_module.MyClass.attr.

Jeśli jesteś ciekawy, jaki jest ich przepis, oto funkcja:

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

Opiera się na pydoc.safeimportfunkcji. Oto dokumenty dotyczące tego:

"""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
źródło
1
Głosowałem za tą odpowiedzią. Przy okazji, tutaj jest kod, który również ma safeimport, ponieważ wydaje się dziwne, aby importować pydoc tylko w tym celu: github.com/python/cpython/blob/ ...
brianray
Głosowałem również za tą odpowiedzią, jest to najlepsza ze wszystkich odpowiedzi.
Sunding Wei
Wydaje się, że jest to w stanie qualnamepoprawnie obsłużyć (obiekt nie znajduje się na górze przestrzeni nazw modułu).
MisterMiyagi
tak, możesz to zrobićlocate('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
źródło
9
po dynamicznym zaimportowaniu modułu masz dostęp do klasy za pośrednictwem modułu
Adam Spence,
Właśnie zredagowałem moją odpowiedź, aby była bardziej zwięzła. To najlepszy sposób na załadowanie klasy w Pythonie.
Adam Spence
BBB-Benny i Spence! ;)
dKen
Wspaniały! Jest nawet częścią standardowej biblioteki od wersji 2.7 i nowszych.
Apteryx
To jest poprawny sposób uzyskiwania dostępu do modułu / klasy. Dokumentacja stwierdza to tutaj: 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
źródło
5
To jest czyste rozwiązanie! Możesz rozważyć użycie:(modulename, classname) = cl.rsplit('.', 2)
vdboor
To świetnie) Stworzyłem pakiet putils z różnymi narzędziami, również tam importując klasy. Jeśli chcesz, możesz go użyć z tego pakietu.
Stan
4
@vdboor rsplit('.', 1)?
Carles Barrobés
Udało mi się przejść {} zamiast globals / locals i nadal działa dobrze
valtron,
17

Jeśli używasz Django, możesz tego użyć. Tak, zdaję sobie sprawę, że OP nie pytał o django, ale natknąłem się na to pytanie szukając rozwiązania Django, nie znalazłem go i umieściłem tutaj dla następnego chłopca / dziewczyny, który go szuka.

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

Pamiętaj, że jeśli chcesz zaimportować coś, co nie ma znaku ., like reani argparse:

re = __import__('re')
Javier Buzzi
źródło
5

Tutaj jest coś, co znalazłem na __import__iimportlib próbując rozwiązać ten problem.

Używam Pythona 3.7.3.

Kiedy próbuję dostać się do zajęć dw module a.b.c,

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

modZmienna odnosi się do górnej przestrzeni nazw a.

Więc żeby dostać się do klasy d, muszę

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

Jeśli spróbujemy

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

faktycznie staramy się szukać a.d.

Kiedy używam importlib, przypuszczam, że biblioteka wykonała getattrza nas rekurencję . Tak więc, kiedy używamy importlib.import_module, faktycznie otrzymujemy uchwyt na najgłębszy moduł.

mod = importlib.import_module('a.b.c') #mod is module c
d = getattr(mod, 'd') #this is a.b.c.d
Richard Wong
źródło
1

OK, dla mnie tak to działało (używam Pythona 2.7):

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

Wtedy b jest instancją klasy „MyClass”

lfvv
źródło
0

Jeśli masz już instancję żądanej klasy, możesz użyć funkcji 'type', aby wyodrębnić jej typ klasy i użyć tego do skonstruowania nowej instancji:

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
źródło
-2
module = __import__("my_package/my_module")
the_class = getattr(module, "MyClass")
obj = the_class()
oefe
źródło
5
Zauważ, że to działa z powodu błędu w funkcji importu . Ścieżki plików nie powinny być używane w funkcji importu i nie będą działać w Pythonie 2.6 i nowszych: docs.python.org/whatsnew/2.6.html#porting-to-python-2-6
Jason Baker
-2

W Google App Engine istnieje webapp2funkcja o nazwie import_string. Więcej informacji można znaleźć tutaj: https://webapp-improved.appspot.com/api/webapp2.html

Więc,

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

Na przykład jest to używane w miejscu, w webapp2.Routektórym można użyć procedury obsługi lub ciągu.

ph0eb0s
źródło