Converter string em objeto de classe Python?

187

Dada uma string como entrada do usuário para uma função Python, eu gostaria de obter um objeto de classe, se houver uma classe com esse nome no espaço para nome definido atualmente. Essencialmente, quero a implementação de uma função que produza esse tipo de resultado:

class Foo:
    pass

str_to_class("Foo")
==> <class __main__.Foo at 0x69ba0>

Tudo isso é possível?


fonte
2
Há algumas respostas úteis aqui, mas eu encontrei a resposta a esta pergunta particularmente útil: stackoverflow.com/questions/452969/...
Tom

Respostas:

115

Aviso : eval()pode ser usado para executar código Python arbitrário. Você nunca deve usar eval()com cordas não confiáveis. (Consulte Segurança do eval () do Python em strings não confiáveis? )

Isso parece mais simples.

>>> class Foo(object):
...     pass
... 
>>> eval("Foo")
<class '__main__.Foo'>
S.Lott
fonte
30
Cuidado com a ramificação do uso de eval. É melhor garantir que a string que você passou não seja do usuário.
overclock
18
O uso de eval () deixa a porta aberta para execução arbitrária de código, por razões de segurança, isso deve ser evitado.
precisa saber é o seguinte
54
Eval não deixa a porta aberta para nada. Se você possui usuários mal-intencionados e mal intencionados, que podem passar mal e com mal valor para avaliar, eles podem apenas editar a fonte python. Como eles podem apenas editar a fonte python, a porta é, foi e sempre estará aberta.
S.Lott
34
A menos que o programa em questão esteja sendo executado em um servidor.
Vatsu1
12
Sinto muito, mas acho que você está dando um péssimo conselho aqui. Considere: Quando o computador solicitar uma senha, o usuário mal-intencionado poderá modificar o executável ou a biblioteca correspondente e ignorar essa verificação. Portanto, pode-se argumentar que é inútil adicionar verificações de senha em primeiro lugar. Ou é?
Daniel Sk
148

Isso poderia funcionar:

import sys

def str_to_class(classname):
    return getattr(sys.modules[__name__], classname)
sexto equipamento
fonte
O que acontecerá se a classe não existir?
riza
7
Ele só irá funcionar para a classe definida no módulo atual
luc
1
Solução realmente boa: aqui você não está usando import, mas um módulo sys mais genérico.
Stefano Falsetto
Isso seria diferente def str_to_class(str): return vars()[str]?
Arne
113

Você poderia fazer algo como:

globals()[class_name]
Laurence Gonsalves
fonte
6
Gosto disso melhor do que a solução 'eval' porque (1) é igualmente fácil e (2) não o deixa aberto à execução arbitrária de código.
Mjumbewu
2
mas você está usando globals()... outra coisa que deve ser evitada
Greg
5
@ Greg Talvez, mas globals()é sem dúvida muito menos ruim do que evale realmente não é pior do que o sys.modules[__name__]AFAIK.
Laurence Gonsalves
2
Sim, entendo o seu ponto, porque você só está pegando algo que não está definindo nada.
Greg
E se a variável for usada em uma classe e várias instâncias dessa classe foram feitas simultaneamente ao mesmo tempo. Isso causará problemas, pois esta é uma variável global.
Alok Kumar Singh
98

Você quer a classe Baz, que vive no módulo foo.bar. Com o Python 2.7, você deseja usar importlib.import_module(), pois isso facilitará a transição para o Python 3:

import importlib

def class_for_name(module_name, class_name):
    # load the module, will raise ImportError if module cannot be loaded
    m = importlib.import_module(module_name)
    # get the class, will raise AttributeError if class cannot be found
    c = getattr(m, class_name)
    return c

Com Python <2.7:

def class_for_name(module_name, class_name):
    # load the module, will raise ImportError if module cannot be loaded
    m = __import__(module_name, globals(), locals(), class_name)
    # get the class, will raise AttributeError if class cannot be found
    c = getattr(m, class_name)
    return c

Usar:

loaded_class = class_for_name('foo.bar', 'Baz')
m.kocikowski
fonte
19
import sys
import types

def str_to_class(field):
    try:
        identifier = getattr(sys.modules[__name__], field)
    except AttributeError:
        raise NameError("%s doesn't exist." % field)
    if isinstance(identifier, (types.ClassType, types.TypeType)):
        return identifier
    raise TypeError("%s is not a class." % field)

Isso manipula com precisão as classes de estilo antigo e novo.

Evan Fosmark
fonte
Você não precisa de types.TypeType; é apenas um alias para o "tipo" embutido.
Glenn Maynard
1
Sim, eu sei, mas acho que é mais claro ler. Eu acho que apenas se resume a preferência, no entanto.
Evan Fosmark
17

Eu olhei como o django lida com isso

django.utils.module_loading tem isso

def import_string(dotted_path):
    """
    Import a dotted module path and return the attribute/class designated by the
    last name in the path. Raise ImportError if the import failed.
    """
    try:
        module_path, class_name = dotted_path.rsplit('.', 1)
    except ValueError:
        msg = "%s doesn't look like a module path" % dotted_path
        six.reraise(ImportError, ImportError(msg), sys.exc_info()[2])

    module = import_module(module_path)

    try:
        return getattr(module, class_name)
    except AttributeError:
        msg = 'Module "%s" does not define a "%s" attribute/class' % (
            module_path, class_name)
        six.reraise(ImportError, ImportError(msg), sys.exc_info()[2])

Você pode usá-lo como import_string("module_path.to.all.the.way.to.your_class")

eugene
fonte
Esta é uma ótima resposta. Aqui está um link para os documentos mais recentes do Django com a implementação atual .
Greg Kaleka 24/01
3

Sim, você pode fazer isso. Supondo que suas classes existam no espaço de nomes global, algo como isso fará isso:

import types

class Foo:
    pass

def str_to_class(s):
    if s in globals() and isinstance(globals()[s], types.ClassType):
            return globals()[s]
    return None

str_to_class('Foo')

==> <class __main__.Foo at 0x340808cc>
ollyc
fonte
1
No Python 3, não há mais ClassType.
riza
1
Use uma instância (x, tipo).
Glenn Maynard
3

Em termos de execução arbitrária de código ou nomes indesejados de usuários, você pode ter uma lista de nomes de funções / classes aceitáveis ​​e, se a entrada corresponder a uma da lista, ela será avaliada.

PS: Eu sei ... meio atrasado ... mas é para quem mais se deparar com isso no futuro.

JoryRFerrell
fonte
9
Se você deseja manter a lista, é melhor manter um ditado do nome para a classe. Então não há necessidade de eval ().
Fasaxc
3

Usar importlib funcionou melhor para mim.

import importlib

importlib.import_module('accounting.views') 

Isso usa a notação de ponto de cadeia para o módulo python que você deseja importar.

Aaron Lelevier
fonte
3

Se você realmente deseja recuperar as classes criadas com uma string, deve armazenar (ou corretamente redigido, referência las ) em um dicionário. Afinal, isso também permitirá nomear suas classes em um nível superior e evitar a exposição de classes indesejadas.

Por exemplo, em um jogo em que classes de ator são definidas em Python e você deseja evitar que outras classes gerais sejam alcançadas pela entrada do usuário.

Outra abordagem (como no exemplo abaixo) seria criar uma nova classe inteira, que contém o que foi dito dictacima. Isso seria:

  • Permitir que vários titulares de classe sejam criados para facilitar a organização (como um para classes de atores e outro para tipos de som);
  • Faça modificações no titular e nas classes que estão sendo realizadas mais facilmente;
  • E você pode usar métodos de classe para adicionar classes ao ditado. (Embora a abstração abaixo não seja realmente necessária, é apenas para ... "ilustração" ).

Exemplo:

class ClassHolder(object):
    def __init__(self):
        self.classes = {}

    def add_class(self, c):
        self.classes[c.__name__] = c

    def __getitem__(self, n):
        return self.classes[n]

class Foo(object):
    def __init__(self):
        self.a = 0

    def bar(self):
        return self.a + 1

class Spam(Foo):
    def __init__(self):
        self.a = 2

    def bar(self):
        return self.a + 4

class SomethingDifferent(object):
    def __init__(self):
        self.a = "Hello"

    def add_world(self):
        self.a += " World"

    def add_word(self, w):
        self.a += " " + w

    def finish(self):
        self.a += "!"
        return self.a

aclasses = ClassHolder()
dclasses = ClassHolder()
aclasses.add_class(Foo)
aclasses.add_class(Spam)
dclasses.add_class(SomethingDifferent)

print aclasses
print dclasses

print "======="
print "o"
print aclasses["Foo"]
print aclasses["Spam"]
print "o"
print dclasses["SomethingDifferent"]

print "======="
g = dclasses["SomethingDifferent"]()
g.add_world()
print g.finish()

print "======="
s = []
s.append(aclasses["Foo"]())
s.append(aclasses["Spam"]())

for a in s:
    print a.a
    print a.bar()
    print "--"

print "Done experiment!"

Isso me retorna:

<__main__.ClassHolder object at 0x02D9EEF0>
<__main__.ClassHolder object at 0x02D9EF30>
=======
o
<class '__main__.Foo'>
<class '__main__.Spam'>
o
<class '__main__.SomethingDifferent'>
=======
Hello World!
=======
0
1
--
2
6
--
Done experiment!

Outro experimento divertido a ver com isso é adicionar um método que atenda às suas necessidades, ClassHolderpara que você nunca perca todas as aulas que fez: ^)

Gustavo6046
fonte