Python tem um equivalente a Java Class.forName ()?

95

Eu preciso pegar um argumento de string e criar um objeto da classe nomeada nessa string em Python. Em Java, eu usaria Class.forName().newInstance(). Existe um equivalente em Python?


Obrigado pelas respostas. Para responder a quem quer saber o que estou fazendo: quero usar um argumento de linha de comando como o nome da classe e instanciá-lo. Na verdade, estou programando em Jython e instanciando classes Java, daí o Java-ness da questão. getattr()funciona bem. Muito obrigado.

Jason
fonte
Consulte stackoverflow.com/a/10675081/116891 para obter um exemplo de uso importlib.import, que foi feito backport de python 3 para 2.7 ( docs.python.org/2/library/importlib.html )
Pat

Respostas:

169

A reflexão em python é muito mais fácil e flexível do que em Java.

Eu recomendo a leitura deste tutorial

Não há nenhuma função direta (que eu saiba) que recebe um nome de classe totalmente qualificado e retorna a classe, no entanto, você tem todas as peças necessárias para construir isso e pode conectá-las.

Porém, um conselho: não tente programar no estilo Java quando estiver em python.

Se você puder explicar o que está tentando fazer, talvez possamos ajudá-lo a encontrar uma maneira mais pítônica de fazer isso.

Esta é uma função que faz o que você deseja:

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

Você pode usar o valor de retorno desta função como se fosse a própria classe.

Aqui está um exemplo de uso:

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

Como isso funciona?

Estamos usando __import__para importar o módulo que contém a classe, o que exigiu que primeiro extraíssemos o nome do módulo do nome totalmente qualificado. Em seguida, importamos o módulo:

m = __import__( module )

Nesse caso, mse referirá apenas ao módulo de nível superior,

Por exemplo, se sua classe reside em um foo.bazmódulo, então mserá o módulo. foo
Podemos facilmente obter uma referência ao foo.bazusogetattr( m, 'baz' )

Para ir do módulo de nível superior para a classe, deve-se usar recursivamente gettatrnas partes do nome da classe

Digamos, por exemplo, se o nome da sua classe é, foo.baz.bar.Modelentão fazemos o seguinte:

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

Isso é o que está acontecendo neste loop:

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

No final do loop, mhaverá uma referência para a aula. Isso significa que mna verdade é a própria classe, você pode fazer, por exemplo:

a = m() #instantiate a new instance of the class    
b = m( arg1, arg2 ) # pass arguments to the constructor
Hassen
fonte
6
Exec é extremamente perigoso. É fácil dar um tiro no próprio pé substituindo nomes em seu osciloscópio dinamicamente e tão fácil quanto abrir uma falha de segurança do tamanho de Rhode Island.
Cody Brocious
1
O motivo do exec é inseguro aqui é que você pode usar um ';' no nome da classe para interromper a importação. Isso pode permitir facilmente a execução de código arbitrário. O motivo pelo qual ele pode danificar seu código é que o exec sobrescreverá nomes em conflito, causando bugs e / ou falhas de segurança.
Cody Brocious
8
Sim, é para isso que __import__existe. Projetos como o Django que fazem magia de carregamento de módulo o usam o tempo todo. Boa resposta.
cdleary
5
get_class = lambda name: reduce(getattr, name.split('.')[1:], __import__(name.partition('.')[0])). Embora 'object.from_name' possa ser um nome melhor. Exemplos: get_class ('decimal.Decimal'), get_class (module_name).
jfs de
1
Bastou definir uma função de sete linhas. Uma verdadeira facilidade.
Pavel Vlasov,
27

Supondo que a classe esteja em seu escopo:

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

De outra forma:

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

Edit: Note, você não pode dar um nome como 'foo.bar' para getattr. Você precisará dividir por. e chame getattr () em cada peça da esquerda para a direita. Isso vai lidar com isso:

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
fonte
Não deveria ser globals () ['classname']?
orip
Você está realmente correto. Esqueci que globals () não retorna o escopo global real, apenas um mapeamento dele. Editando como tal - obrigado!
Cody Brocious
Isso não é equivalente a Class.forName de java, em Java, nenhuma suposição é feita sobre o que é importado e o que não é
hasen
1
attrs = 'foo.bar.baz'.split('.'); fooBar = reduce(getattr, attrs[1:], __import__(attrs[0]))
jfs de
1
Oumodule, _, 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

Uso

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: 
Pat
fonte
1
+1 Para usar importlib para fazer isso, uma vez que evita a necessidade (que causa a importação ) de encontrar recursivamente a classe. Mais simples e mais limpa do que a resposta aceita (embora menos bem documentada).
sage88 de
4

Mais uma implementação.

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
fonte
O if module_nameLBYL é bastante redundantes, uma vez que o erro que você ganha se você simplesmente apagar essas linhas é: ValueError: Empty module name.
bukzor
3

Parece que você está abordando isso pelo meio em vez do começo. O que você realmente está tentando fazer? Encontrar a classe associada a uma determinada string é um meio para um fim.

Se você esclarecer seu problema, o que pode exigir sua própria refatoração mental, uma solução melhor pode se apresentar.

Por exemplo: Você está tentando carregar um objeto salvo com base em seu nome de tipo e um conjunto de parâmetros? Python soletra isso despickling e você deve olhar para o módulo pickle . E mesmo que o processo de remoção faça exatamente o que você descreve, você não precisa se preocupar com como ele funciona internamente:

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

fonte
1
isso é muito interessante, mas é um pouco diferente da pergunta
Nick
1

Ele é encontrado na biblioteca padrão do python, como unittest.TestLoader.loadTestsFromName. Infelizmente, o método continua fazendo atividades adicionais relacionadas ao teste, mas este primeiro parece reutilizável. Eu editei para remover a funcionalidade relacionada ao teste:

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

Eu precisava obter objetos para todas as classes existentes em my_package. Então, eu importo todas as classes necessárias para my_packageo de __init__.py.

Portanto, minha estrutura de diretório é assim:

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

E minha __init__.pyaparência é assim:

from .module1 import ClassA
from .module2 import ClassB

Então eu crio uma função como esta:

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

Onde module_name = 'my_package'

inspecione o documento: https://docs.python.org/3/library/inspect.html#inspect.getmembers

Sushant Chaudhary
fonte