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

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

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


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

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

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)

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

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

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'


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


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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.