Instansiasi dinamis dari nama string suatu kelas dalam modul yang diimpor secara dinamis?

179

Dalam python, saya harus instantiate kelas tertentu, mengetahui namanya dalam sebuah string, tetapi kelas ini 'hidup' dalam modul yang diimpor secara dinamis. Contoh berikut:

skrip kelas loader:

import sys
class loader:
  def __init__(self, module_name, class_name): # both args are strings
    try:
      __import__(module_name)
      modul = sys.modules[module_name]
      instance = modul.class_name() # obviously this doesn't works, here is my main problem!
    except ImportError:
       # manage import error

beberapa skrip modul yang dinamis:

class myName:
  # etc...

Saya menggunakan pengaturan ini untuk membuat modul apa pun yang dimuat secara dinamis untuk digunakan oleh kelas pemuat mengikuti perilaku tertentu yang telah ditentukan sebelumnya dalam modul yang dimuat-din ...

Javier Novoa C.
sumber

Jawaban:

251

Anda bisa menggunakan getattr

getattr(module, class_name)

untuk mengakses kelas. Kode lebih lengkap:

module = __import__(module_name)
class_ = getattr(module, class_name)
instance = class_()

Seperti yang disebutkan di bawah ini , kami dapat menggunakan importlib

import importlib
module = importlib.import_module(module_name)
class_ = getattr(module, class_name)
instance = class_()
Sven Marnach
sumber
3
module = __import__(module, fromlist=[name])hanya bekerja untukku.
umpirsky
16
Jika ada yang mengalami masalah dengan metode impor Sven yang disebutkan di atas, saya menemukan kode saya bekerja lebih baik menggunakan metode berikut ini, bukan importlib.import_module . Dapat digunakan seperti: module = importlib.import_module (module_name)
jpennell
@ jpennell Anda harus memposting itu sebagai jawaban, sering kali lebih bermanfaat untuk dapat langsung menggunakan string yang dikembalikan olehobj.__module__
Anentropic
importlib.import_moduleakan memuat file .py ke dalam pyc jika diperlukan dan juga menangani module.name.pathing.to.get.to.the yang lengkap. __import__tidak akan melakukan salah satu dari hal-hal ini, dalam lingkungan Django (tidak diuji di luar ini)
James
122

tl; dr

Impor modul root dengan importlib.import_moduledan muat kelas dengan namanya menggunakan getattrfungsi:

# Standard import
import importlib
# Load "module.submodule.MyClass"
MyClass = getattr(importlib.import_module("module.submodule"), "MyClass")
# Instantiate the class (pass arguments to the constructor, if needed)
instance = MyClass()

penjelasan

Anda mungkin tidak ingin menggunakan __import__untuk secara dinamis mengimpor modul dengan nama, karena tidak memungkinkan Anda untuk mengimpor submodul:

>>> mod = __import__("os.path")
>>> mod.join
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'join'

Inilah yang dikatakan python doc tentang __import__:

Catatan: Ini adalah fungsi lanjutan yang tidak diperlukan dalam pemrograman Python sehari-hari, tidak seperti importlib.import_module ().

Sebagai gantinya, gunakan importlibmodul standar untuk mengimpor modul secara dinamis dengan nama. Dengan getattrAnda kemudian dapat instantiate kelas dengan namanya:

import importlib
my_module = importlib.import_module("module.submodule")
MyClass = getattr(my_module, "MyClass")
instance = MyClass()

Anda juga bisa menulis:

import importlib
module_name, class_name = "module.submodule.MyClass".rsplit(".", 1)
MyClass = getattr(importlib.import_module(module_name), class_name)
instance = MyClass()

Kode ini valid dalam python ≥ 2.7 (termasuk python 3).

Régis B.
sumber
1
mungkin saya tidak mengerti jawaban Anda, tetapi saya telah menggunakan impor untuk mengimpor submodules: __import __ ("module." + submodule_name_string)
Javier Novoa C.
Kode berikut ini menghasilkan AttributeError: mod = __import__("os.path"); mod.joinsementara yang berikut tidak:mod = importlib.import_module("os.path"); mod.join
Régis B.
oh saya mengerti, Anda benar tetapi saya telah melakukan yang berikut untuk mendapatkan metode os.path.join: getattr (sys.modules ["os.path"], "join")
Javier Novoa C.
Ha! dan saya melihat bahwa ketika menggunakan itu, maka ' impor ' tidak perlu XD
Javier Novoa C.
2
Pilihan yang ditandai sebagai jawaban tidak berfungsi untuk saya (menggunakan submodul) namun jawaban ini tidak. Saya pikir itu harus menjadi jawaban karena ini adalah solusi yang lebih umum untuk memuat modul secara dinamis.
rkachach
14

Gunakan getattruntuk mendapatkan atribut dari nama dalam sebuah string. Dengan kata lain, dapatkan instance sebagai

instance = getattr(modul, class_name)()
AFoglia
sumber
10

Salin-tempel cuplikan:

import importlib
def str_to_class(module_name, class_name):
    """Return a class instance from a string reference"""
    try:
        module_ = importlib.import_module(module_name)
        try:
            class_ = getattr(module_, class_name)()
        except AttributeError:
            logging.error('Class does not exist')
    except ImportError:
        logging.error('Module does not exist')
    return class_ or None
Andrejs Cainikovs
sumber
5

Jika Anda ingin kalimat ini from foo.bar import foo2dimuat secara dinamis, Anda harus melakukan ini

foo = __import__("foo")
bar = getattr(foo,"bar")
foo2 = getattr(bar,"foo2")

instance = foo2()
Ahmad Muzakki
sumber
4

Seseorang dapat menggunakan pydoc.locatefungsi tersebut.

from pydoc import locate
my_class = locate("module.submodule.myclass")
instance = my_class()
Xema
sumber
2

Saya tidak bisa sampai di sana dalam kasus penggunaan saya dari contoh di atas, tetapi Ahmad mendapatkan saya yang paling dekat (terima kasih). Bagi mereka yang membaca ini di masa depan, berikut adalah kode yang berfungsi untuk saya.

def get_class(fully_qualified_path, module_name, class_name, *instantiation):
    """
    Returns an instantiated class for the given string descriptors
    :param fully_qualified_path: The path to the module eg("Utilities.Printer")
    :param module_name: The module name eg("Printer")
    :param class_name: The class name eg("ScreenPrinter")
    :param instantiation: Any fields required to instantiate the class
    :return: An instance of the class
    """
    p = __import__(fully_qualified_path)
    m = getattr(p, module_name)
    c = getattr(m, class_name)
    instance = c(*instantiation)
    return instance
SteveJ
sumber