Apakah python memiliki padanan dengan Java Class.forName ()?

95

Saya memiliki kebutuhan untuk mengambil argumen string dan membuat objek kelas yang dinamai dalam string itu dengan Python. Di Jawa, saya akan menggunakan Class.forName().newInstance(). Apakah ada yang setara dengan Python?


Terima kasih atas tanggapannya. Untuk menjawab mereka yang ingin tahu apa yang saya lakukan: Saya ingin menggunakan argumen baris perintah sebagai nama kelas, dan membuatnya. Saya sebenarnya memprogram dalam Jython dan membuat instance kelas Java, oleh karena itu pertanyaan Java-an. getattr()bekerja dengan baik. Terimakasih banyak.

Jason
sumber
Lihat stackoverflow.com/a/10675081/116891 untuk contoh penggunaan importlib.import, yang di-backport dari python 3 ke 2.7 ( docs.python.org/2/library/importlib.html )
Pat

Jawaban:

169

Refleksi dengan python jauh lebih mudah dan jauh lebih fleksibel daripada di Java.

Saya merekomendasikan membaca tutorial ini

Tidak ada fungsi langsung (yang saya ketahui) yang mengambil nama kelas yang memenuhi syarat dan mengembalikan kelas, namun Anda memiliki semua bagian yang diperlukan untuk membangunnya, dan Anda dapat menghubungkannya bersama.

Sedikit saran: jangan mencoba memprogram dengan gaya Java saat Anda menggunakan python.

Jika Anda dapat menjelaskan apa yang Anda coba lakukan, mungkin kami dapat membantu Anda menemukan cara yang lebih pythonic untuk melakukannya.

Inilah fungsi yang melakukan apa yang Anda inginkan:

def get_class( kls ):
    parts = kls.split('.')
    module = ".".join(parts[:-1])
    m = __import__( module )
    for comp in parts[1:]:
        m = getattr(m, comp)            
    return m

Anda dapat menggunakan nilai hasil dari fungsi ini seolah-olah itu adalah kelas itu sendiri.

Berikut contoh penggunaan:

>>> D = get_class("datetime.datetime")
>>> D
<type 'datetime.datetime'>
>>> D.now()
datetime.datetime(2009, 1, 17, 2, 15, 58, 883000)
>>> a = D( 2010, 4, 22 )
>>> a
datetime.datetime(2010, 4, 22, 0, 0)
>>> 

Bagaimana cara kerjanya?

Kami menggunakan __import__untuk mengimpor modul yang menampung kelas, yang mengharuskan kami mengekstrak nama modul dari nama yang sepenuhnya memenuhi syarat. Kemudian kami mengimpor modul:

m = __import__( module )

Pada kasus ini, m hanya akan mengacu pada modul tingkat atas,

Misalnya, jika kelas Anda tinggal di foo.bazmodul, maka makan menjadi modul. foo
Kita dapat dengan mudah mendapatkan referensi untuk foo.bazdigunakangetattr( m, 'baz' )

Untuk mendapatkan dari modul tingkat atas ke kelas, harus menggunakan secara rekursif gettatr nama kelas

Katakanlah misalnya, jika nama kelas Anda foo.baz.bar.Modelmaka kami melakukan ini:

m = __import__( "foo.baz.bar" ) #m is package foo
m = getattr( m, "baz" ) #m is package baz
m = getattr( m, "bar" ) #m is module bar
m = getattr( m, "Model" ) #m is class Model

Inilah yang terjadi di loop ini:

for comp in parts[1:]:
    m = getattr(m, comp)    

Di akhir loop, makan menjadi referensi ke kelas. Ini berarti itu msebenarnya adalah kelasnya sendiri, Anda dapat melakukannya misalnya:

a = m() #instantiate a new instance of the class    
b = m( arg1, arg2 ) # pass arguments to the constructor
hasen
sumber
6
Exec sangat berbahaya. Sangat mudah untuk menembak diri sendiri dengan menimpa nama dalam lingkup Anda secara dinamis dan semudah membuka celah keamanan sebesar Rhode Island.
Cody Brocious
1
Alasan exec tidak aman di sini adalah karena Anda dapat menggunakan ';' dalam nama kelas untuk keluar dari impor. Ini dapat dengan mudah memungkinkan eksekusi kode arbitrer. Alasan itu dapat merusak kode Anda adalah karena exec akan menimpa nama yang bertabrakan, menyebabkan bug dan / atau kelemahan keamanan.
Cody Brocious
8
Ya, untuk itulah __import__ada. Proyek seperti Django yang melakukan module-loading-magic menggunakannya sepanjang waktu. Jawaban bagus.
cdleary
5
get_class = lambda name: reduce(getattr, name.split('.')[1:], __import__(name.partition('.')[0])). Meskipun 'object.from_name' mungkin nama yang lebih baik. Contoh: get_class ('decimal.Decimal'), get_class (module_name).
jfs
1
Hanya perlu mendefinisikan fungsi tujuh baris. Sangat mudah.
Pavel Vlasov
27

Dengan asumsi kelas berada dalam lingkup Anda:

globals()['classname'](args, to, constructor)

Jika tidak:

getattr(someModule, 'classname')(args, to, constructor)

Edit: Catatan, Anda tidak bisa memberi nama seperti 'foo.bar' ke getattr. Anda harus membaginya. dan panggil getattr () pada setiap bagian kiri-ke-kanan. Ini akan menangani itu:

module, rest = 'foo.bar.baz'.split('.', 1)
fooBar = reduce(lambda a, b: getattr(a, b), rest.split('.'), globals()[module])
someVar = fooBar(args, to, constructor)
Cody Brocious
sumber
Bukankah seharusnya global () ['classname']?
orip
Anda memang benar. Saya lupa globals () tidak mengembalikan cakupan global sebenarnya, hanya pemetaannya. Mengedit seperti itu - terima kasih!
Cody Brocious
Ini tidak sama dengan Class.forName java, di Java, tidak ada asumsi yang dibuat tentang apa yang diimpor dan apa yang tidak
hasen
1
attrs = 'foo.bar.baz'.split('.'); fooBar = reduce(getattr, attrs[1:], __import__(attrs[0]))
jfs
1
Ataumodule, _, attrs = 'foo.bar.baz'.partition('.'); fooBar = reduce(getattr, attrs, __import__(module))
jfs
15
def import_class_from_string(path):
    from importlib import import_module
    module_path, _, class_name = path.rpartition('.')
    mod = import_module(module_path)
    klass = getattr(mod, class_name)
    return klass

Pemakaian

In [59]: raise import_class_from_string('google.appengine.runtime.apiproxy_errors.DeadlineExceededError')()
---------------------------------------------------------------------------
DeadlineExceededError                     Traceback (most recent call last)
<ipython-input-59-b4e59d809b2f> in <module>()
----> 1 raise import_class_from_string('google.appengine.runtime.apiproxy_errors.DeadlineExceededError')()

DeadlineExceededError: 
Menepuk
sumber
1
+1 Untuk menggunakan importlib untuk melakukan ini karena mencegah kebutuhan ( penyebab impor itu ) untuk menemukan kelas secara rekursif. Lebih sederhana dan lebih bersih daripada jawaban yang diterima (meskipun kurang terdokumentasi dengan baik).
sage88
4

Implementasi lain.

def import_class(class_string):
    """Returns class object specified by a string.

    Args:
        class_string: The string representing a class.

    Raises:
        ValueError if module part of the class is not specified.
    """
    module_name, _, class_name = class_string.rpartition('.')
    if module_name == '':
        raise ValueError('Class name must contain module part.')
    return getattr(
        __import__(module_name, globals(), locals(), [class_name], -1),
        class_name)
greyhemp
sumber
The if module_nameLBYL cukup redudant, karena kesalahan yang Anda dapatkan jika Anda hanya menghapus garis adalah: ValueError: Empty module name.
bukzor
3

Sepertinya Anda mendekati ini dari tengah, bukan dari awal. Apa yang sebenarnya kamu coba lakukan? Menemukan kelas yang terkait dengan string yang diberikan adalah sarana untuk mencapai tujuan.

Jika Anda memperjelas masalah Anda, yang mungkin memerlukan refactoring mental Anda sendiri, solusi yang lebih baik mungkin muncul dengan sendirinya.

Misalnya: Apakah Anda mencoba memuat objek yang disimpan berdasarkan nama jenisnya dan sekumpulan parameter? Python mengeja kata ini dan Anda harus melihat modul acar . Dan meskipun proses pembongkaran melakukan persis seperti yang Anda gambarkan, Anda tidak perlu khawatir tentang cara kerjanya secara internal:

>>> class A(object):
...   def __init__(self, v):
...     self.v = v
...   def __reduce__(self):
...     return (self.__class__, (self.v,))
>>> a = A("example")
>>> import pickle
>>> b = pickle.loads(pickle.dumps(a))
>>> a.v, b.v
('example', 'example')
>>> a is b
False

sumber
1
ini sangat menarik, tetapi sedikit berbeda dari pertanyaannya
Nick
1

Ini ditemukan di pustaka standar python, sebagai unittest.TestLoader.loadTestsFromName. Sayangnya, metode ini terus melakukan aktivitas terkait pengujian tambahan, tetapi ha pertama ini tampaknya dapat digunakan kembali. Saya telah mengeditnya untuk menghapus fungsionalitas terkait pengujian:

def get_object(name):
    """Retrieve a python object, given its dotted.name."""
    parts = name.split('.')
    parts_copy = parts[:]
    while parts_copy:
        try:
            module = __import__('.'.join(parts_copy))
            break
        except ImportError:
            del parts_copy[-1]
            if not parts_copy: raise
    parts = parts[1:]

    obj = module
    for part in parts:
        parent, obj = obj, getattr(obj, part)

    return obj
bukzor.dll
sumber
0

Saya perlu mendapatkan objek untuk semua kelas yang ada di my_package. Jadi saya mengimpor semua kelas yang diperlukan ke my_packagedalam __init__.py.

Jadi struktur direktori saya seperti ini:

/my_package
    - __init__.py
    - module1.py
    - module2.py
    - module3.py

Dan __init__.pypenampilanku seperti ini:

from .module1 import ClassA
from .module2 import ClassB

Kemudian saya membuat fungsi seperti ini:

def get_classes_from_module_name(module_name):
    return [_cls() for _, _cls in inspect.getmembers(__import__(module_name), inspect.isclass)]

Dimana module_name = 'my_package'

periksa dokumen: https://docs.python.org/3/library/inspect.html#inspect.getmembers

Sushant Chaudhary
sumber