Есть ли у python эквивалент Java Class.forName ()?


95

Мне нужно взять строковый аргумент и создать объект класса, названного в этой строке, в Python. В Java я бы использовал Class.forName().newInstance(). Есть ли эквивалент в Python?


Спасибо за ответы. Чтобы ответить тем, кто хочет знать, что я делаю: я хочу использовать аргумент командной строки в качестве имени класса и создать его экземпляр. На самом деле я программирую на Jython и создаю экземпляры классов Java, отсюда и 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 () ['classname']?
orip

Вы действительно правы. Я забыл, что globals () не возвращает фактическую глобальную область видимости, а только ее отображение. Редактирование как таковое - спасибо!
Cody Brocious

Это не эквивалентно классу Java Class.forName, в 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.
букзор 05

3

Кажется, вы подходите к этому с середины, а не с начала. Что ты на самом деле пытаешься сделать? Поиск класса, связанного с данной строкой, является средством достижения цели.

Если вы проясните свою проблему, которая может потребовать вашего собственного мысленного рефакторинга, лучшее решение может появиться само собой.

Например: вы пытаетесь загрузить сохраненный объект на основе его имени типа и набора параметров? Python произносит это «unpickling», и вам стоит взглянуть на модуль pickle . И хотя процесс распаковки делает именно то, что вы описываете, вам не нужно беспокоиться о том, как он работает внутри:

>>> 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. К сожалению, метод продолжает выполнять дополнительные действия, связанные с тестированием, но это первое ha выглядит пригодным для повторного использования. Я отредактировал его, чтобы удалить функции, связанные с тестами:

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«с __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.