Чи має python еквівалент Java Class.forName ()?


95

Мені потрібно взяти аргумент рядка і створити об'єкт класу, названого в цьому рядку в Python. У Java я б використовував Class.forName().newInstance(). Чи існує еквівалент у Python?


Дякую за відповіді. Щоб відповісти тим, хто хоче знати, що я роблю: я хочу використовувати аргумент командного рядка як ім'я класу та створити його. Я насправді програмую в Jython і створюю інстанції для класів Java, отже, і суть питання. getattr()чудово працює. Велике спасибі.


Див stackoverflow.com/a/10675081/116891 для прикладу використання importlib.import, який був успадкований з Python 3 до 2,7 ( docs.python.org/2/library/importlib.html )
Pat

Відповіді:


169

Відображення в python набагато легше і набагато гнучкіше, ніж у Java.

Рекомендую прочитати цей посібник

Немає прямої функції (про яку я знаю), яка приймає повністю кваліфіковане ім'я класу і повертає клас, однак у вас є всі частини, необхідні для його створення, і ви можете з'єднати їх разом.

Однак одна порада: не намагайтеся програмувати в стилі Java, коли ви в python.

Якщо ви можете пояснити, що саме ви намагаєтесь зробити, можливо, ми можемо допомогти вам знайти більш пітонічний спосіб зробити це.

Ось функція, яка робить те, що ви хочете:

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

Ви можете використовувати повернене значення цієї функції так, ніби це сам клас.

Ось приклад використання:

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

Як це працює?

Ми використовуємо __import__для імпорту модуля, що містить клас, що вимагало спочатку витягти ім’я модуля з повністю кваліфікованого імені. Потім ми імпортуємо модуль:

m = __import__( module )

У цьому випадку mбуде посилатися лише на модуль верхнього рівня,

Наприклад, якщо ваш клас живе в foo.bazмодулі, тоді mце буде модуль. foo
Ми можемо легко отримати посилання на foo.bazвикористанняgetattr( m, 'baz' )

Щоб перейти від модуля верхнього рівня до класу, потрібно рекурсивно використовувати gettatrна частинах імені класу

Скажімо, наприклад, якщо ви маєте назву класу, foo.baz.bar.Modelтоді ми робимо це:

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

Ось що відбувається в цьому циклі:

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

В кінці циклу mбуде посилання на клас. Це означає, що mнасправді це клас itslef, ви можете зробити, наприклад:

a = m() #instantiate a new instance of the class    
b = m( arg1, arg2 ) # pass arguments to the constructor

6
Exec надзвичайно небезпечний. Легко стріляти собі в ногу, динамічно переписуючи імена у вашому полі, і так само легко відкрити дефект безпеки розміром з Род-Айленд.
Cody Brocious

1
Причина, по якій exec є небезпечним, полягає в тому, що ви можете використовувати ';' в назві класу, щоб вийти з імпорту. Це може легко дозволити довільне виконання коду. Причиною того, що це може пошкодити ваш код, є те, що exec перезапише зіткнулися імена, що спричинить помилки та / або недоліки безпеки.
Cody Brocious

8
Так, для цього __import__існує. Такі проекти, як Django, які використовують магію завантаження модулів, використовують її постійно. Приємна відповідь.
cdleary

5
get_class = lambda name: reduce(getattr, name.split('.')[1:], __import__(name.partition('.')[0])). Хоча 'object.from_name' може бути і кращою назвою. Приклади: get_class ('decimal.Decimal'), get_class (module_name).
jfs

1
Потрібно було лише визначити функцію із семи рядків. Справжня легкість.
Павло Власов

27

Якщо припустити, що клас знаходиться у вашій сфері:

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

В іншому випадку:

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

Редагувати: Зверніть увагу, що ви не можете вказати ім'я типу foo.bar для getattr Вам потрібно буде розділити його на. і викликайте getattr () на кожному фрагменті зліва направо. Це дозволить:

module, rest = 'foo.bar.baz'.split('.', 1)
fooBar = reduce(lambda a, b: getattr(a, b), rest.split('.'), globals()[module])
someVar = fooBar(args, to, constructor)

Це не має бути globals () ['ім'я класу']?
orip

Ви справді праві. Я забув, що globals () не повертає фактичну глобальну область дії, лише її відображення. Редагування як таке - спасибі!
Cody Brocious

Це не еквівалентно Class.forName у Java, на Java не робиться припущень щодо того, що імпортується, а що ні
hasen

1
attrs = 'foo.bar.baz'.split('.'); fooBar = reduce(getattr, attrs[1:], __import__(attrs[0]))
jfs

1
Абоmodule, _, 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

Використання

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: 

1
+1 Для використання importlib для цього, оскільки це запобігає необхідності (що викликає імпорт ) рекурсивно знаходити клас. Простіше і чіткіше прийнятої відповіді (хоча і менш добре задокументовано).
sage88

4

Ще одна реалізація.

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)

if module_nameLBYL досить redudant, так як помилка , яку ви отримаєте , якщо ви просто видаліть ці рядки є: ValueError: Empty module name.
bukzor

3

Здається, ви підходите до цього із середини, а не з початку. Що ви насправді намагаєтесь зробити? Пошук класу, пов'язаного з даним рядком, є засобом досягнення мети.

Якщо ви з’ясуєте свою проблему, яка може зажадати вашої власної психічної рефакторингу, може з’явитися краще рішення.

Наприклад: Ви намагаєтесь завантажити збережений об'єкт на основі його імені типу та набору параметрів? Python пише це неповторне написання, і вам слід поглянути на модуль розсолу . І незважаючи на те, що процес видалення виконує саме те, що ви описуєте, вам не доведеться турбуватися про те, як він працює внутрішньо:

>>> 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

1
це дуже цікаво, але дещо інше, ніж питання
Нік

1

Це знаходиться в стандартній бібліотеці python як unittest.TestLoader.loadTestsFromName. На жаль, метод продовжує виконувати додаткові заходи, пов’язані з тестами, але цей перший виглядає повторно. Я відредагував його, щоб видалити функціональність, пов’язану з тестом:

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

0

Мені потрібно було отримати об'єкти для всіх існуючих класів у my_package. Тому я імпортую всі необхідні класи в my_package's __init__.py.

Отже, моя структура каталогів така:

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

А я __init__.pyвиглядаю так:

from .module1 import ClassA
from .module2 import ClassB

Потім я створюю таку функцію:

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

Де module_name = 'my_package'

перевірити документ: https://docs.python.org/3/library/inspect.html#inspect.getmembers

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.