Cara memuat kelas Python secara dinamis

167

Diberikan string dari kelas Python, misalnya my_package.my_module.MyClass, apa cara terbaik untuk memuatnya?

Dengan kata lain saya mencari yang setara Class.forName()di Jawa, berfungsi dengan Python. Perlu bekerja di Google App Engine.

Lebih disukai ini adalah fungsi yang menerima FQN dari kelas sebagai string, dan mengembalikan referensi ke kelas:

my_class = load_class('my_package.my_module.MyClass')
my_instance = my_class()
pjesi
sumber
Saya harus dapat menetapkan referensi kelas ke variabel juga.
pjesi
1
Ini tampaknya merupakan duplikat dari: stackoverflow.com/questions/452969/…
cdleary
Anda benar itu adalah duplikat, terima kasih telah menemukannya
pjesi
1
Saya tidak pernah perlu memuat kelas seperti ini dengan Python. Anda tahu di mana modul itu jadi mengapa tidak hanya memuat modul dan kemudian gunakan kelas-kelasnya seperti yang diinginkan Python, jauh lebih sederhana
Adam Spence
22
Jika Anda tidak pernah perlu melakukan ini maka Anda belum menulis program yang cukup menarik. Terus berlatih.
John Tyree

Jawaban:

194

Dari dokumentasi python, inilah fungsi yang Anda inginkan:

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

Alasan sederhana __import__tidak akan bekerja adalah karena setiap impor apa pun melewati titik pertama dalam paket string adalah atribut dari modul yang Anda impor. Dengan demikian, sesuatu seperti ini tidak akan berfungsi:

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

Anda harus memanggil fungsi di atas seperti:

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

Atau dalam hal contoh Anda:

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

EDIT : Saya agak kecewa dengan hal ini. Apa yang pada dasarnya ingin Anda lakukan adalah ini:

from my_package.my_module import my_class

Fungsi di atas hanya diperlukan jika Anda memiliki daftar kosong . Dengan demikian, panggilan yang tepat akan seperti ini:

mod = __import__('my_package.my_module', fromlist=['my_class'])
klass = getattr(mod, 'my_class')
Jason Baker
sumber
Saya mencoba my_import ('my_package.my_module.my_class') tetapi tidak mendapatkan modul yang ditemukan my_class, yang masuk akal karena ini adalah kelas bukan modul. Howver jika saya dapat menggunakan gettattr untuk mendapatkan kelas setelah panggilan untuk my_import
pjesi
Itu aneh. Segala sesuatu yang melewati titik pertama disebut menggunakan getattr. Seharusnya tidak ada perbedaan.
Jason Baker
Terima kasih saya pikir ini adalah cara terbaik. Sekarang aku hanya perlu cara terbaik untuk membagi string 'my_pakcage.my_module.my_class' ke mod_name, klass_name tapi saya kira saya bisa mencari tahu :)
pjesi
2
dokumentasi python (pada kode) impor mengatakan untuk menggunakan importlib. jadi sebaiknya
periksa
1
Tautan @naoko merujuk ke: docs.python.org/3/library/importlib.html#importlib.__import__
Noel Evans
125

Jika Anda tidak ingin roll sendiri, ada fungsi yang tersedia di pydocmodul yang melakukan ini:

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

Keuntungan dari pendekatan ini atas yang lain yang tercantum di sini adalah bahwa locateakan menemukan setiap objek python di jalan putus-putus yang disediakan, bukan hanya obyek langsung dalam sebuah modul. mis my_package.my_module.MyClass.attr.

Jika Anda ingin tahu apa resep mereka, inilah fungsinya:

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

Itu bergantung pada pydoc.safeimportfungsi. Inilah dokumen untuk itu:

"""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
sumber
1
Saya membatalkan jawaban ini. BTW, berikut adalah kode yang juga memiliki safeimport karena tampaknya aneh untuk mengimpor pydoc hanya untuk ini: github.com/python/cpython/blob/…
brianray
Saya membatalkan jawaban ini juga, ini adalah yang terbaik dari semua jawaban yang relevan.
Sunding Wei
Ini tampaknya dapat menangani qualname(objek tidak di atas modul namespace) dengan benar juga.
MisterMiyagi
ya, Anda bisa melakukannyalocate('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
sumber
9
setelah Anda mengimpor modul secara dinamis, Anda memiliki akses ke kelas melalui modul
Adam Spence
Edit saja jawaban saya untuk lebih ringkas. Ini adalah cara terbaik untuk memuat kelas dengan Python.
Adam Spence
BBB-Benny dan Spence! ;)
dKen
Bagus! Itu bahkan bagian dari perpustakaan standar dari 2.7 ke atas.
Apteryx
Ini adalah cara yang benar untuk mengakses modul / kelas. Dokumen menyatakan ini di sini: 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
sumber
5
Ini solusi bersih! Anda dapat mempertimbangkan untuk menggunakan:(modulename, classname) = cl.rsplit('.', 2)
vdboor
Sangat bagus) Saya telah membuat paket putils dengan berbagai utilitas, juga kelas yang mengimpor di sana. Jika mau, Anda bisa menggunakannya dari paket itu.
Stan
4
@vdboor rsplit('.', 1)?
Carles Barrobés
Saya berhasil lulus {} alih-alih global / lokal dan masih berfungsi dengan baik
valtron
17

Jika Anda menggunakan Django, Anda dapat menggunakan ini. Ya saya sadar OP tidak meminta Django, tetapi saya menemukan pertanyaan ini untuk mencari solusi Django, tidak menemukan satu, dan menaruhnya di sini untuk anak laki-laki / perempuan berikutnya yang mencarinya.

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

Ingat, jika Anda ingin mengimpor sesuatu yang tidak memiliki ., suka reatauargparse gunakan:

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

Ini untuk membagikan sesuatu yang saya temukan di __import__danimportlib saat mencoba menyelesaikan masalah ini.

Saya menggunakan Python 3.7.3.

Ketika saya mencoba untuk masuk ke kelas ddalam modul a.b.c,

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

The modvariabel merujuk ke puncak namespace a.

Jadi untuk sampai ke kelas d, saya harus

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

Jika kita coba lakukan

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

kami sebenarnya sedang mencari a.d.

Saat menggunakan importlib, saya kira perpustakaan telah melakukan rekursif getattruntuk kita. Jadi, ketika kita gunakan importlib.import_module, kita benar-benar mendapatkan pegangan pada modul terdalam.

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

OK, bagi saya itu cara kerjanya (saya menggunakan Python 2.7):

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

Kemudian, b adalah turunan dari kelas 'MyClass'

lfvv
sumber
0

Jika Anda telah memiliki instance dari kelas yang Anda inginkan, Anda dapat menggunakan fungsi 'type' untuk mengekstrak tipe kelasnya dan menggunakan ini untuk membuat instance baru:

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
sumber
-2
module = __import__("my_package/my_module")
the_class = getattr(module, "MyClass")
obj = the_class()
Oefe
sumber
5
Perhatikan bahwa ini berfungsi karena ada bug dalam fungsi impor . Jalur file tidak boleh digunakan dalam fungsi impor dan tidak akan berfungsi dalam python 2.6 dan di atas: docs.python.org/whatsnew/2.6.html#porting-to-python-2-6
Jason Baker
-2

Di Google App Engine ada webapp2fungsi yang disebut import_string. Untuk info lebih lanjut lihat di sini: https://webapp-improved.appspot.com/api/webapp2.html

Begitu,

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

Misalnya ini digunakan di webapp2.Routemana Anda bisa menggunakan pawang atau string.

ph0eb0s
sumber