Como carregar dinamicamente uma classe Python

167

Dada uma string de uma classe Python, por exemplo my_package.my_module.MyClass, qual é a melhor maneira possível de carregá-la?

Em outras palavras, estou procurando um equivalente Class.forName()em Java, função em Python. Ele precisa funcionar no Google App Engine.

De preferência, essa seria uma função que aceita o FQN da classe como uma sequência e retorna uma referência à classe:

my_class = load_class('my_package.my_module.MyClass')
my_instance = my_class()
pjesi
fonte
Eu preciso ser capaz de atribuir a referência de classe a uma variável também.
pjesi
1
Isso parece ser uma duplicata de: stackoverflow.com/questions/452969/… #
cdleary
Você está certo, é uma duplicata, obrigado por encontrá-la
pjesi
1
Eu nunca precisei carregar uma classe como esta em Python. Você sabe onde o módulo é assim porque não basta carregar o módulo e, em seguida, usar suas classes como Python quer que você, sua muito mais simples
Adam Spence
22
Se você nunca precisou fazer isso, ainda não criou programas suficientemente interessantes. Continue praticando.
21410 John Mayer

Respostas:

194

Na documentação do python, aqui está a função que você deseja:

def my_import(name):
    components = name.split('.')
    mod = __import__(components[0])
    for comp in components[1:]:
        mod = getattr(mod, comp)
    return mod

A razão pela qual um simples __import__não funciona é porque qualquer importação de algo além do primeiro ponto em uma cadeia de pacotes é um atributo do módulo que você está importando. Assim, algo assim não funcionará:

__import__('foo.bar.baz.qux')

Você teria que chamar a função acima da seguinte maneira:

my_import('foo.bar.baz.qux')

Ou no caso do seu exemplo:

klass = my_import('my_package.my_module.my_class')
some_object = klass()

Edição : Eu estava um pouco fora sobre isso. O que você basicamente quer fazer é o seguinte:

from my_package.my_module import my_class

A função acima é necessária apenas se você tiver uma lista em branco vazia . Assim, a chamada apropriada seria assim:

mod = __import__('my_package.my_module', fromlist=['my_class'])
klass = getattr(mod, 'my_class')
Jason Baker
fonte
Tentei my_import ('my_package.my_module.my_class'), mas não foi encontrado nenhum módulo my_class, o que faz sentido, pois é uma classe e não um módulo. Howver se eu posso usar gettattr para obter a classe após a chamada para my_import
pjesi
Isso é estranho. Tudo após o primeiro ponto é chamado usando getattr. Não deveria haver nenhuma diferença.
Jason Baker
Obrigado, acho que é o melhor caminho. Agora eu só preciso a melhor maneira de dividir a string 'my_pakcage.my_module.my_class' em mod_name, klass_name mas eu acho que eu posso descobrir isso :)
pjesi
2
A documentação python (no código) da importação diz para usar importlib. resposta de check-out por Adam Spence
naoko 9/01/19
1
Link @naoko está se referindo a: docs.python.org/3/library/importlib.html#importlib.__import__
Noel Evans
125

Se você não deseja criar o seu próprio, existe uma função disponível no pydocmódulo que faz exatamente isso:

from pydoc import locate
my_class = locate('my_package.my_module.MyClass')

A vantagem dessa abordagem sobre as outras listadas aqui é que locatevocê encontrará qualquer objeto python no caminho pontilhado fornecido, não apenas um objeto diretamente dentro de um módulo. por exemplo my_package.my_module.MyClass.attr.

Se você está curioso para saber qual é a receita deles, aqui está a função:

def locate(path, forceload=0):
    """Locate an object by name or dotted path, importing as necessary."""
    parts = [part for part in split(path, '.') if part]
    module, n = None, 0
    while n < len(parts):
        nextmodule = safeimport(join(parts[:n+1], '.'), forceload)
        if nextmodule: module, n = nextmodule, n + 1
        else: break
    if module:
        object = module
    else:
        object = __builtin__
    for part in parts[n:]:
        try:
            object = getattr(object, part)
        except AttributeError:
            return None
    return object

Depende da pydoc.safeimportfunção. Aqui estão os documentos para isso:

"""Import a module; handle errors; return None if the module isn't found.

If the module *is* found but an exception occurs, it's wrapped in an
ErrorDuringImport exception and reraised.  Unlike __import__, if a
package path is specified, the module at the end of the path is returned,
not the package at the beginning.  If the optional 'forceload' argument
is 1, we reload the module from disk (unless it's a dynamic extension)."""
chadrik
fonte
1
Votou esta resposta. BTW, aqui está o código que também têm safeimport como parece estranho para pydoc importação apenas para isso: github.com/python/cpython/blob/...
brianray
Também votei nessa resposta, esta é a melhor de todas as respostas relevantes.
Sunding Wei
Isso parece ser capaz de manipular qualname(objeto que não está no topo do espaço para nome do módulo) corretamente também.
MisterMiyagi
sim, você pode fazerlocate('my_package.my_module.MyClass.attr.method.etc')
chadrik
109
import importlib

module = importlib.import_module('my_package.my_module')
my_class = getattr(module, 'MyClass')
my_instance = my_class()
Adam Spence
fonte
9
uma vez que você importou o módulo dinamicamente você tem acesso à classe através do módulo
Adam Spence
Acabei de editar minha resposta para ser mais concisa. Esta é a melhor maneira de carregar uma classe em Python.
Adam Spence
BBB-Benny e o Spence! ;)
dKen
Ótimo! É até parte da biblioteca padrão a partir da 2.7.
Apteryx
Esta é a maneira correta de acessar um módulo / classe. Os documentos declaram isso aqui: docs.python.org/3/library/importlib.html#importlib.__import__
Noel Evans
29
def import_class(cl):
    d = cl.rfind(".")
    classname = cl[d+1:len(cl)]
    m = __import__(cl[0:d], globals(), locals(), [classname])
    return getattr(m, classname)
Stan
fonte
5
Esta é a solução limpa! Você pode considerar o uso de:(modulename, classname) = cl.rsplit('.', 2)
vdboor 19/06/12
É ótimo) eu criei o pacote putils com diferentes utilitários, também importando classes para lá. Se quiser, você pode usá-lo nesse pacote.
Stan
4
@vdboor rsplit('.', 1)?
Carles Barrobés 02/04
Consegui passar {} ao invés de globais / moradores e ainda funciona bem
valtron
17

Se você estiver usando o Django, você pode usar isso. Sim, eu sei que o OP não pediu django, mas eu me deparei com essa pergunta procurando uma solução Django, não encontrei uma e a coloquei aqui para o próximo garoto / garota que a procura.

# It's available for v1.7+
# https://github.com/django/django/blob/stable/1.7.x/django/utils/module_loading.py
from django.utils.module_loading import import_string

Klass = import_string('path.to.module.Klass')
func = import_string('path.to.module.func')
var = import_string('path.to.module.var')

Lembre-se de que se deseja importar algo que não possui ., gosta reou argparseusa:

re = __import__('re')
Javier Buzzi
fonte
5

Aqui está para compartilhar algo que eu encontrei __import__e importlibenquanto tentava resolver esse problema.

Estou usando o Python 3.7.3.

Quando tento chegar à aula dno módulo a.b.c,

mod = __import__('a.b.c')

A modvariável refere-se ao espaço para nome superior a.

Então, para chegar à aula d, eu preciso

mod = getattr(mod, 'b') #mod is now module b
mod = getattr(mod, 'c') #mod is now module c
mod = getattr(mod, 'd') #mod is now class d

Se tentarmos fazer

mod = __import__('a.b.c')
d = getattr(mod, 'd')

na verdade, estamos tentando procurar a.d.

Ao usar importlib, suponho que a biblioteca tenha sido recursiva getattrpor nós. Então, quando usamos importlib.import_module, realmente identificamos o módulo mais profundo.

mod = importlib.import_module('a.b.c') #mod is module c
d = getattr(mod, 'd') #this is a.b.c.d
Richard Wong
fonte
1

OK, para mim, é assim que funciona (estou usando o Python 2.7):

a = __import__('file_to_import', globals(), locals(), ['*'], -1)
b = a.MyClass()

Então, b é uma instância da classe 'MyClass'

lfvv
fonte
0

Se você já possui uma instância da sua classe desejada, pode usar a função 'type' para extrair seu tipo de classe e usá-la para construir uma nova instância:

class Something(object):
    def __init__(self, name):
        self.name = name
    def display(self):
        print(self.name)

one = Something("one")
one.display()
cls = type(one)
two = cls("two")
two.display()
Jason DeMorrow
fonte
-2
module = __import__("my_package/my_module")
the_class = getattr(module, "MyClass")
obj = the_class()
oefe
fonte
5
Observe que isso funciona devido a um erro na função de importação . Caminhos de arquivo deve não ser utilizado na importação função e não vai funcionar em Python 2.6 e acima: docs.python.org/whatsnew/2.6.html#porting-to-python-2-6
Jason Baker
-2

No Google App Engine, há uma webapp2função chamada import_string. Para mais informações, consulte aqui: https://webapp-improved.appspot.com/api/webapp2.html

Assim,

import webapp2
my_class = webapp2.import_string('my_package.my_module.MyClass')

Por exemplo, isso é usado no webapp2.Routelocal em que você pode usar um manipulador ou uma string.

ph0eb0s
fonte